1use super::{Capability, CapabilityLocalization, CapabilityStatus};
19use crate::tool_types::ToolHints;
20use crate::tools::{Tool, ToolExecutionResult};
21use crate::traits::ToolContext;
22use async_trait::async_trait;
23use serde::{Deserialize, Serialize};
24use serde_json::{Value, json};
25
26pub const FAKE_WAREHOUSE_CAPABILITY_ID: &str = "fake_warehouse";
27
28pub struct FakeWarehouseCapability;
30
31impl Capability for FakeWarehouseCapability {
32 fn id(&self) -> &str {
33 FAKE_WAREHOUSE_CAPABILITY_ID
34 }
35
36 fn name(&self) -> &str {
37 "Fake Warehouse Tools"
38 }
39
40 fn description(&self) -> &str {
41 "Demo capability: warehouse management tools (inventory, shipments, orders, invoices, returns). State stored in session filesystem."
42 }
43
44 fn localizations(&self) -> Vec<CapabilityLocalization> {
45 vec![CapabilityLocalization::text(
46 "uk",
47 "Демо-інструменти складу",
48 "Демонстраційна можливість: інструменти керування складом (запаси, відвантаження, замовлення, рахунки, повернення). Стан зберігається у файловій системі сесії.",
49 )]
50 }
51
52 fn status(&self) -> CapabilityStatus {
53 CapabilityStatus::Available
54 }
55
56 fn icon(&self) -> Option<&str> {
57 Some("package")
58 }
59
60 fn category(&self) -> Option<&str> {
61 Some("Demo Tools")
62 }
63
64 fn system_prompt_addition(&self) -> Option<&str> {
65 Some(
66 "Warehouse data is stored in /warehouse/ (inventory.json, shipments.json, orders.json, invoices.json, returns.json).",
67 )
68 }
69
70 fn tools(&self) -> Vec<Box<dyn Tool>> {
71 vec![
72 Box::new(WarehouseGetInventoryTool),
73 Box::new(WarehouseUpdateInventoryTool),
74 Box::new(WarehouseCreateShipmentTool),
75 Box::new(WarehouseListShipmentsTool),
76 Box::new(WarehouseUpdateShipmentStatusTool),
77 Box::new(WarehouseCreateOrderTool),
78 Box::new(WarehouseListOrdersTool),
79 Box::new(WarehouseCreateInvoiceTool),
80 Box::new(WarehouseProcessReturnTool),
81 Box::new(WarehouseInventoryReportTool),
82 ]
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88struct InventoryItem {
89 sku: String,
90 name: String,
91 quantity: i32,
92 location: String,
93 reorder_point: i32,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97struct Shipment {
98 id: String,
99 status: String, destination: String,
101 items: Vec<ShipmentItem>,
102 created_at: String,
103 updated_at: String,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107struct ShipmentItem {
108 sku: String,
109 quantity: i32,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113struct Order {
114 id: String,
115 customer_name: String,
116 items: Vec<ShipmentItem>,
117 status: String,
118 created_at: String,
119}
120
121pub struct WarehouseGetInventoryTool;
126
127#[async_trait]
128impl Tool for WarehouseGetInventoryTool {
129 fn name(&self) -> &str {
130 "warehouse_get_inventory"
131 }
132
133 fn display_name(&self) -> Option<&str> {
134 Some("Get Inventory")
135 }
136
137 fn description(&self) -> &str {
138 "Get current inventory levels. Optionally filter by SKU or show only low stock items."
139 }
140
141 fn parameters_schema(&self) -> Value {
142 json!({
143 "type": "object",
144 "properties": {
145 "sku": {
146 "type": "string",
147 "description": "Optional: Get inventory for a specific SKU"
148 },
149 "low_stock_only": {
150 "type": "boolean",
151 "description": "Optional: Show only items below reorder point"
152 }
153 },
154 "additionalProperties": false
155 })
156 }
157
158 fn hints(&self) -> ToolHints {
159 ToolHints::default()
160 .with_readonly(true)
161 .with_idempotent(true)
162 }
163
164 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
165 ToolExecutionResult::tool_error(
166 "warehouse_get_inventory requires context. Must be executed with session context.",
167 )
168 }
169
170 async fn execute_with_context(
171 &self,
172 arguments: Value,
173 context: &ToolContext,
174 ) -> ToolExecutionResult {
175 let file_store = match &context.file_store {
176 Some(store) => store,
177 None => return ToolExecutionResult::tool_error("File system not available"),
178 };
179
180 let sku_filter = arguments.get("sku").and_then(|v| v.as_str());
181 let low_stock_only = arguments
182 .get("low_stock_only")
183 .and_then(|v| v.as_bool())
184 .unwrap_or(false);
185
186 let inventory: Vec<InventoryItem> = match file_store
188 .read_file(context.session_id, "/warehouse/inventory.json")
189 .await
190 {
191 Ok(Some(file)) => serde_json::from_str(file.content.as_deref().unwrap_or(""))
192 .unwrap_or_else(|_| {
193 vec![
195 InventoryItem {
196 sku: "WH-001".to_string(),
197 name: "Industrial Widget".to_string(),
198 quantity: 150,
199 location: "A1".to_string(),
200 reorder_point: 50,
201 },
202 InventoryItem {
203 sku: "WH-002".to_string(),
204 name: "Premium Gadget".to_string(),
205 quantity: 30,
206 location: "B2".to_string(),
207 reorder_point: 40,
208 },
209 InventoryItem {
210 sku: "WH-003".to_string(),
211 name: "Standard Component".to_string(),
212 quantity: 200,
213 location: "C3".to_string(),
214 reorder_point: 75,
215 },
216 ]
217 }),
218 _ => {
219 let initial = vec![
221 InventoryItem {
222 sku: "WH-001".to_string(),
223 name: "Industrial Widget".to_string(),
224 quantity: 150,
225 location: "A1".to_string(),
226 reorder_point: 50,
227 },
228 InventoryItem {
229 sku: "WH-002".to_string(),
230 name: "Premium Gadget".to_string(),
231 quantity: 30,
232 location: "B2".to_string(),
233 reorder_point: 40,
234 },
235 InventoryItem {
236 sku: "WH-003".to_string(),
237 name: "Standard Component".to_string(),
238 quantity: 200,
239 location: "C3".to_string(),
240 reorder_point: 75,
241 },
242 ];
243
244 let content = serde_json::to_string_pretty(&initial).unwrap();
246 let _ = file_store
247 .write_file(
248 context.session_id,
249 "/warehouse/inventory.json",
250 &content,
251 "text",
252 )
253 .await;
254
255 initial
256 }
257 };
258
259 let mut filtered: Vec<_> = inventory.into_iter().collect();
261
262 if let Some(sku) = sku_filter {
263 filtered.retain(|item| item.sku == sku);
264 }
265
266 if low_stock_only {
267 filtered.retain(|item| item.quantity < item.reorder_point);
268 }
269
270 ToolExecutionResult::success(json!({
271 "inventory": filtered,
272 "total_items": filtered.len(),
273 "low_stock_count": filtered.iter().filter(|item| item.quantity < item.reorder_point).count()
274 }))
275 }
276
277 fn requires_context(&self) -> bool {
278 true
279 }
280}
281
282pub struct WarehouseUpdateInventoryTool;
287
288#[async_trait]
289impl Tool for WarehouseUpdateInventoryTool {
290 fn name(&self) -> &str {
291 "warehouse_update_inventory"
292 }
293
294 fn display_name(&self) -> Option<&str> {
295 Some("Update Inventory")
296 }
297
298 fn description(&self) -> &str {
299 "Update inventory quantity for a product. Use positive numbers to add stock, negative to remove."
300 }
301
302 fn parameters_schema(&self) -> Value {
303 json!({
304 "type": "object",
305 "properties": {
306 "sku": {
307 "type": "string",
308 "description": "Product SKU to update"
309 },
310 "quantity_change": {
311 "type": "integer",
312 "description": "Quantity to add (positive) or remove (negative)"
313 },
314 "reason": {
315 "type": "string",
316 "description": "Reason for update (e.g., 'restock', 'sale', 'damage')"
317 }
318 },
319 "required": ["sku", "quantity_change"],
320 "additionalProperties": false
321 })
322 }
323
324 fn hints(&self) -> ToolHints {
325 ToolHints::default().with_idempotent(true)
326 }
327
328 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
329 ToolExecutionResult::tool_error("warehouse_update_inventory requires context")
330 }
331
332 async fn execute_with_context(
333 &self,
334 arguments: Value,
335 context: &ToolContext,
336 ) -> ToolExecutionResult {
337 let file_store = match &context.file_store {
338 Some(store) => store,
339 None => return ToolExecutionResult::tool_error("File system not available"),
340 };
341
342 let sku = match arguments.get("sku").and_then(|v| v.as_str()) {
343 Some(s) => s,
344 None => return ToolExecutionResult::tool_error("Missing required parameter: sku"),
345 };
346
347 let quantity_change = match arguments.get("quantity_change").and_then(|v| v.as_i64()) {
348 Some(q) => q as i32,
349 None => {
350 return ToolExecutionResult::tool_error(
351 "Missing required parameter: quantity_change",
352 );
353 }
354 };
355
356 let reason = arguments
357 .get("reason")
358 .and_then(|v| v.as_str())
359 .unwrap_or("manual update");
360
361 let mut inventory: Vec<InventoryItem> = match file_store
363 .read_file(context.session_id, "/warehouse/inventory.json")
364 .await
365 {
366 Ok(Some(file)) => {
367 serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
368 }
369 _ => vec![],
370 };
371
372 let item = match inventory.iter_mut().find(|item| item.sku == sku) {
374 Some(i) => i,
375 None => return ToolExecutionResult::tool_error(format!("SKU not found: {}", sku)),
376 };
377
378 let old_quantity = item.quantity;
379 item.quantity += quantity_change;
380
381 if item.quantity < 0 {
382 item.quantity = old_quantity; return ToolExecutionResult::tool_error("Insufficient inventory");
384 }
385
386 let new_quantity = item.quantity;
387 let reorder_point = item.reorder_point;
388 let content = serde_json::to_string_pretty(&inventory).unwrap();
390 match file_store
391 .write_file(
392 context.session_id,
393 "/warehouse/inventory.json",
394 &content,
395 "text",
396 )
397 .await
398 {
399 Ok(_) => ToolExecutionResult::success(json!({
400 "sku": sku,
401 "old_quantity": old_quantity,
402 "new_quantity": new_quantity,
403 "change": quantity_change,
404 "reason": reason,
405 "below_reorder_point": new_quantity < reorder_point
406 })),
407 Err(e) => ToolExecutionResult::internal_error(e),
408 }
409 }
410
411 fn requires_context(&self) -> bool {
412 true
413 }
414}
415
416pub struct WarehouseCreateShipmentTool;
421
422#[async_trait]
423impl Tool for WarehouseCreateShipmentTool {
424 fn name(&self) -> &str {
425 "warehouse_create_shipment"
426 }
427
428 fn display_name(&self) -> Option<&str> {
429 Some("Create Shipment")
430 }
431
432 fn description(&self) -> &str {
433 "Create a new shipment. Automatically updates inventory levels."
434 }
435
436 fn parameters_schema(&self) -> Value {
437 json!({
438 "type": "object",
439 "properties": {
440 "destination": {
441 "type": "string",
442 "description": "Shipment destination address"
443 },
444 "items": {
445 "type": "array",
446 "description": "Items to ship",
447 "items": {
448 "type": "object",
449 "properties": {
450 "sku": {"type": "string"},
451 "quantity": {"type": "integer", "minimum": 1}
452 },
453 "required": ["sku", "quantity"]
454 }
455 }
456 },
457 "required": ["destination", "items"],
458 "additionalProperties": false
459 })
460 }
461
462 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
463 ToolExecutionResult::tool_error("warehouse_create_shipment requires context")
464 }
465
466 async fn execute_with_context(
467 &self,
468 arguments: Value,
469 context: &ToolContext,
470 ) -> ToolExecutionResult {
471 let file_store = match &context.file_store {
472 Some(store) => store,
473 None => return ToolExecutionResult::tool_error("File system not available"),
474 };
475
476 let destination = match arguments.get("destination").and_then(|v| v.as_str()) {
477 Some(d) => d,
478 None => {
479 return ToolExecutionResult::tool_error("Missing required parameter: destination");
480 }
481 };
482
483 let items: Vec<ShipmentItem> = match arguments.get("items") {
484 Some(items_value) => match serde_json::from_value(items_value.clone()) {
485 Ok(items) => items,
486 Err(_) => return ToolExecutionResult::tool_error("Invalid items format"),
487 },
488 None => return ToolExecutionResult::tool_error("Missing required parameter: items"),
489 };
490
491 let mut shipments: Vec<Shipment> = match file_store
493 .read_file(context.session_id, "/warehouse/shipments.json")
494 .await
495 {
496 Ok(Some(file)) => {
497 serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
498 }
499 _ => vec![],
500 };
501
502 let shipment_id = format!("SHP-{:05}", shipments.len() + 1);
504 let now = chrono::Utc::now().to_rfc3339();
505
506 let shipment = Shipment {
507 id: shipment_id.clone(),
508 status: "pending".to_string(),
509 destination: destination.to_string(),
510 items: items.clone(),
511 created_at: now.clone(),
512 updated_at: now,
513 };
514
515 shipments.push(shipment.clone());
516
517 let content = serde_json::to_string_pretty(&shipments).unwrap();
519 match file_store
520 .write_file(
521 context.session_id,
522 "/warehouse/shipments.json",
523 &content,
524 "text",
525 )
526 .await
527 {
528 Ok(_) => ToolExecutionResult::success(json!({
529 "shipment_id": shipment_id,
530 "status": "pending",
531 "destination": destination,
532 "items": items,
533 "message": "Shipment created successfully"
534 })),
535 Err(e) => ToolExecutionResult::internal_error(e),
536 }
537 }
538
539 fn requires_context(&self) -> bool {
540 true
541 }
542}
543
544pub struct WarehouseListShipmentsTool;
549
550#[async_trait]
551impl Tool for WarehouseListShipmentsTool {
552 fn name(&self) -> &str {
553 "warehouse_list_shipments"
554 }
555
556 fn display_name(&self) -> Option<&str> {
557 Some("List Shipments")
558 }
559
560 fn description(&self) -> &str {
561 "List all shipments. Optionally filter by status."
562 }
563
564 fn parameters_schema(&self) -> Value {
565 json!({
566 "type": "object",
567 "properties": {
568 "status": {
569 "type": "string",
570 "enum": ["pending", "in_transit", "delivered"],
571 "description": "Optional: Filter by shipment status"
572 }
573 },
574 "additionalProperties": false
575 })
576 }
577
578 fn hints(&self) -> ToolHints {
579 ToolHints::default()
580 .with_readonly(true)
581 .with_idempotent(true)
582 }
583
584 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
585 ToolExecutionResult::tool_error("warehouse_list_shipments requires context")
586 }
587
588 async fn execute_with_context(
589 &self,
590 arguments: Value,
591 context: &ToolContext,
592 ) -> ToolExecutionResult {
593 let file_store = match &context.file_store {
594 Some(store) => store,
595 None => return ToolExecutionResult::tool_error("File system not available"),
596 };
597
598 let status_filter = arguments.get("status").and_then(|v| v.as_str());
599
600 let shipments: Vec<Shipment> = match file_store
602 .read_file(context.session_id, "/warehouse/shipments.json")
603 .await
604 {
605 Ok(Some(file)) => {
606 serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
607 }
608 _ => vec![],
609 };
610
611 let filtered: Vec<_> = if let Some(status) = status_filter {
613 shipments
614 .into_iter()
615 .filter(|s| s.status == status)
616 .collect()
617 } else {
618 shipments
619 };
620
621 ToolExecutionResult::success(json!({
622 "shipments": filtered,
623 "total_count": filtered.len()
624 }))
625 }
626
627 fn requires_context(&self) -> bool {
628 true
629 }
630}
631
632pub struct WarehouseUpdateShipmentStatusTool;
637
638#[async_trait]
639impl Tool for WarehouseUpdateShipmentStatusTool {
640 fn name(&self) -> &str {
641 "warehouse_update_shipment_status"
642 }
643
644 fn display_name(&self) -> Option<&str> {
645 Some("Update Shipment Status")
646 }
647
648 fn description(&self) -> &str {
649 "Update the status of a shipment (pending -> in_transit -> delivered)."
650 }
651
652 fn parameters_schema(&self) -> Value {
653 json!({
654 "type": "object",
655 "properties": {
656 "shipment_id": {
657 "type": "string",
658 "description": "Shipment ID to update"
659 },
660 "status": {
661 "type": "string",
662 "enum": ["pending", "in_transit", "delivered"],
663 "description": "New status"
664 }
665 },
666 "required": ["shipment_id", "status"],
667 "additionalProperties": false
668 })
669 }
670
671 fn hints(&self) -> ToolHints {
672 ToolHints::default().with_idempotent(true)
673 }
674
675 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
676 ToolExecutionResult::tool_error("warehouse_update_shipment_status requires context")
677 }
678
679 async fn execute_with_context(
680 &self,
681 arguments: Value,
682 context: &ToolContext,
683 ) -> ToolExecutionResult {
684 let file_store = match &context.file_store {
685 Some(store) => store,
686 None => return ToolExecutionResult::tool_error("File system not available"),
687 };
688
689 let shipment_id = match arguments.get("shipment_id").and_then(|v| v.as_str()) {
690 Some(id) => id,
691 None => {
692 return ToolExecutionResult::tool_error("Missing required parameter: shipment_id");
693 }
694 };
695
696 let new_status = match arguments.get("status").and_then(|v| v.as_str()) {
697 Some(s) => s,
698 None => return ToolExecutionResult::tool_error("Missing required parameter: status"),
699 };
700
701 let mut shipments: Vec<Shipment> = match file_store
703 .read_file(context.session_id, "/warehouse/shipments.json")
704 .await
705 {
706 Ok(Some(file)) => {
707 serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
708 }
709 _ => vec![],
710 };
711
712 let shipment = match shipments.iter_mut().find(|s| s.id == shipment_id) {
714 Some(s) => s,
715 None => {
716 return ToolExecutionResult::tool_error(format!(
717 "Shipment not found: {}",
718 shipment_id
719 ));
720 }
721 };
722
723 let old_status = shipment.status.clone();
724 shipment.status = new_status.to_string();
725 let updated_at = shipment.updated_at.clone();
726 shipment.updated_at = chrono::Utc::now().to_rfc3339();
727
728 let content = serde_json::to_string_pretty(&shipments).unwrap();
730 match file_store
731 .write_file(
732 context.session_id,
733 "/warehouse/shipments.json",
734 &content,
735 "text",
736 )
737 .await
738 {
739 Ok(_) => ToolExecutionResult::success(json!({
740 "shipment_id": shipment_id,
741 "old_status": old_status,
742 "new_status": new_status,
743 "updated_at": updated_at
744 })),
745 Err(e) => ToolExecutionResult::internal_error(e),
746 }
747 }
748
749 fn requires_context(&self) -> bool {
750 true
751 }
752}
753
754pub struct WarehouseCreateOrderTool;
759
760#[async_trait]
761impl Tool for WarehouseCreateOrderTool {
762 fn name(&self) -> &str {
763 "warehouse_create_order"
764 }
765
766 fn display_name(&self) -> Option<&str> {
767 Some("Create Order")
768 }
769
770 fn description(&self) -> &str {
771 "Create a new customer order."
772 }
773
774 fn parameters_schema(&self) -> Value {
775 json!({
776 "type": "object",
777 "properties": {
778 "customer_name": {"type": "string"},
779 "items": {
780 "type": "array",
781 "items": {
782 "type": "object",
783 "properties": {
784 "sku": {"type": "string"},
785 "quantity": {"type": "integer"}
786 }
787 }
788 }
789 },
790 "required": ["customer_name", "items"],
791 "additionalProperties": false
792 })
793 }
794
795 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
796 ToolExecutionResult::tool_error("Requires context")
797 }
798
799 async fn execute_with_context(
800 &self,
801 arguments: Value,
802 context: &ToolContext,
803 ) -> ToolExecutionResult {
804 let file_store = match &context.file_store {
805 Some(store) => store,
806 None => return ToolExecutionResult::tool_error("File system not available"),
807 };
808
809 let customer_name = arguments
810 .get("customer_name")
811 .and_then(|v| v.as_str())
812 .unwrap_or("Unknown");
813 let items: Vec<ShipmentItem> =
814 serde_json::from_value(arguments.get("items").cloned().unwrap_or(json!([])))
815 .unwrap_or_default();
816
817 let mut orders: Vec<Order> = match file_store
818 .read_file(context.session_id, "/warehouse/orders.json")
819 .await
820 {
821 Ok(Some(file)) => {
822 serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
823 }
824 _ => vec![],
825 };
826
827 let order_id = format!("ORD-{:05}", orders.len() + 1);
828 let order = Order {
829 id: order_id.clone(),
830 customer_name: customer_name.to_string(),
831 items: items.clone(),
832 status: "pending".to_string(),
833 created_at: chrono::Utc::now().to_rfc3339(),
834 };
835
836 orders.push(order);
837
838 let content = serde_json::to_string_pretty(&orders).unwrap();
839 let _ = file_store
840 .write_file(
841 context.session_id,
842 "/warehouse/orders.json",
843 &content,
844 "text",
845 )
846 .await;
847
848 ToolExecutionResult::success(
849 json!({"order_id": order_id, "status": "pending", "items": items}),
850 )
851 }
852
853 fn requires_context(&self) -> bool {
854 true
855 }
856}
857
858pub struct WarehouseListOrdersTool;
859
860#[async_trait]
861impl Tool for WarehouseListOrdersTool {
862 fn name(&self) -> &str {
863 "warehouse_list_orders"
864 }
865
866 fn display_name(&self) -> Option<&str> {
867 Some("List Orders")
868 }
869
870 fn description(&self) -> &str {
871 "List all customer orders."
872 }
873
874 fn parameters_schema(&self) -> Value {
875 json!({"type": "object", "properties": {}, "additionalProperties": false})
876 }
877
878 fn hints(&self) -> ToolHints {
879 ToolHints::default()
880 .with_readonly(true)
881 .with_idempotent(true)
882 }
883
884 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
885 ToolExecutionResult::tool_error("Requires context")
886 }
887
888 async fn execute_with_context(
889 &self,
890 _arguments: Value,
891 context: &ToolContext,
892 ) -> ToolExecutionResult {
893 let file_store = match &context.file_store {
894 Some(store) => store,
895 None => return ToolExecutionResult::tool_error("File system not available"),
896 };
897
898 let orders: Vec<Order> = match file_store
899 .read_file(context.session_id, "/warehouse/orders.json")
900 .await
901 {
902 Ok(Some(file)) => {
903 serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
904 }
905 _ => vec![],
906 };
907
908 ToolExecutionResult::success(json!({"orders": orders, "total_count": orders.len()}))
909 }
910
911 fn requires_context(&self) -> bool {
912 true
913 }
914}
915
916pub struct WarehouseCreateInvoiceTool;
917
918#[async_trait]
919impl Tool for WarehouseCreateInvoiceTool {
920 fn name(&self) -> &str {
921 "warehouse_create_invoice"
922 }
923
924 fn display_name(&self) -> Option<&str> {
925 Some("Create Invoice")
926 }
927
928 fn description(&self) -> &str {
929 "Generate an invoice for an order."
930 }
931
932 fn parameters_schema(&self) -> Value {
933 json!({
934 "type": "object",
935 "properties": {
936 "order_id": {"type": "string"}
937 },
938 "required": ["order_id"],
939 "additionalProperties": false
940 })
941 }
942
943 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
944 ToolExecutionResult::tool_error("Requires context")
945 }
946
947 async fn execute_with_context(
948 &self,
949 arguments: Value,
950 _context: &ToolContext,
951 ) -> ToolExecutionResult {
952 let order_id = arguments
953 .get("order_id")
954 .and_then(|v| v.as_str())
955 .unwrap_or("unknown");
956 let invoice_id = format!("INV-{:05}", chrono::Utc::now().timestamp() % 100000);
957
958 ToolExecutionResult::success(json!({
959 "invoice_id": invoice_id,
960 "order_id": order_id,
961 "amount": 1299.99,
962 "status": "generated"
963 }))
964 }
965
966 fn requires_context(&self) -> bool {
967 true
968 }
969}
970
971pub struct WarehouseProcessReturnTool;
972
973#[async_trait]
974impl Tool for WarehouseProcessReturnTool {
975 fn name(&self) -> &str {
976 "warehouse_process_return"
977 }
978
979 fn display_name(&self) -> Option<&str> {
980 Some("Process Return")
981 }
982
983 fn description(&self) -> &str {
984 "Process a product return and update inventory."
985 }
986
987 fn parameters_schema(&self) -> Value {
988 json!({
989 "type": "object",
990 "properties": {
991 "order_id": {"type": "string"},
992 "sku": {"type": "string"},
993 "quantity": {"type": "integer"},
994 "reason": {"type": "string"}
995 },
996 "required": ["order_id", "sku", "quantity"],
997 "additionalProperties": false
998 })
999 }
1000
1001 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
1002 ToolExecutionResult::tool_error("Requires context")
1003 }
1004
1005 async fn execute_with_context(
1006 &self,
1007 arguments: Value,
1008 _context: &ToolContext,
1009 ) -> ToolExecutionResult {
1010 let return_id = format!("RET-{:05}", chrono::Utc::now().timestamp() % 100000);
1011 let sku = arguments
1012 .get("sku")
1013 .and_then(|v| v.as_str())
1014 .unwrap_or("unknown");
1015
1016 ToolExecutionResult::success(json!({
1017 "return_id": return_id,
1018 "sku": sku,
1019 "status": "processed",
1020 "inventory_updated": true
1021 }))
1022 }
1023
1024 fn requires_context(&self) -> bool {
1025 true
1026 }
1027}
1028
1029pub struct WarehouseInventoryReportTool;
1030
1031#[async_trait]
1032impl Tool for WarehouseInventoryReportTool {
1033 fn name(&self) -> &str {
1034 "warehouse_inventory_report"
1035 }
1036
1037 fn display_name(&self) -> Option<&str> {
1038 Some("Inventory Report")
1039 }
1040
1041 fn description(&self) -> &str {
1042 "Generate a comprehensive inventory report with metrics and alerts."
1043 }
1044
1045 fn parameters_schema(&self) -> Value {
1046 json!({"type": "object", "properties": {}, "additionalProperties": false})
1047 }
1048
1049 fn hints(&self) -> ToolHints {
1050 ToolHints::default()
1051 .with_readonly(true)
1052 .with_idempotent(true)
1053 }
1054
1055 async fn execute(&self, _arguments: Value) -> ToolExecutionResult {
1056 ToolExecutionResult::tool_error("Requires context")
1057 }
1058
1059 async fn execute_with_context(
1060 &self,
1061 _arguments: Value,
1062 context: &ToolContext,
1063 ) -> ToolExecutionResult {
1064 let file_store = match &context.file_store {
1065 Some(store) => store,
1066 None => return ToolExecutionResult::tool_error("File system not available"),
1067 };
1068
1069 let inventory: Vec<InventoryItem> = match file_store
1070 .read_file(context.session_id, "/warehouse/inventory.json")
1071 .await
1072 {
1073 Ok(Some(file)) => {
1074 serde_json::from_str(file.content.as_deref().unwrap_or("")).unwrap_or_default()
1075 }
1076 _ => vec![],
1077 };
1078
1079 let total_items = inventory.len();
1080 let low_stock: Vec<_> = inventory
1081 .iter()
1082 .filter(|i| i.quantity < i.reorder_point)
1083 .collect();
1084 let total_value: i32 = inventory.iter().map(|i| i.quantity).sum();
1085
1086 ToolExecutionResult::success(json!({
1087 "report": {
1088 "total_items": total_items,
1089 "total_quantity": total_value,
1090 "low_stock_count": low_stock.len(),
1091 "low_stock_items": low_stock,
1092 "generated_at": chrono::Utc::now().to_rfc3339()
1093 }
1094 }))
1095 }
1096
1097 fn requires_context(&self) -> bool {
1098 true
1099 }
1100}