machinaos 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/.env.template +71 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/bin/cli.js +159 -0
  5. package/client/.dockerignore +45 -0
  6. package/client/Dockerfile +68 -0
  7. package/client/eslint.config.js +29 -0
  8. package/client/index.html +13 -0
  9. package/client/nginx.conf +66 -0
  10. package/client/package.json +48 -0
  11. package/client/src/App.tsx +27 -0
  12. package/client/src/Dashboard.tsx +1173 -0
  13. package/client/src/ParameterPanel.tsx +301 -0
  14. package/client/src/components/AIAgentNode.tsx +321 -0
  15. package/client/src/components/APIKeyValidator.tsx +118 -0
  16. package/client/src/components/ClaudeChatModelNode.tsx +18 -0
  17. package/client/src/components/ConditionalEdge.tsx +189 -0
  18. package/client/src/components/CredentialsModal.tsx +306 -0
  19. package/client/src/components/EdgeConditionEditor.tsx +443 -0
  20. package/client/src/components/GeminiChatModelNode.tsx +18 -0
  21. package/client/src/components/GenericNode.tsx +357 -0
  22. package/client/src/components/LocationParameterPanel.tsx +154 -0
  23. package/client/src/components/ModelNode.tsx +286 -0
  24. package/client/src/components/OpenAIChatModelNode.tsx +18 -0
  25. package/client/src/components/OutputPanel.tsx +471 -0
  26. package/client/src/components/ParameterRenderer.tsx +1874 -0
  27. package/client/src/components/SkillEditorModal.tsx +417 -0
  28. package/client/src/components/SquareNode.tsx +797 -0
  29. package/client/src/components/StartNode.tsx +250 -0
  30. package/client/src/components/ToolkitNode.tsx +365 -0
  31. package/client/src/components/TriggerNode.tsx +463 -0
  32. package/client/src/components/auth/LoginPage.tsx +247 -0
  33. package/client/src/components/auth/ProtectedRoute.tsx +59 -0
  34. package/client/src/components/base/BaseChatModelNode.tsx +271 -0
  35. package/client/src/components/icons/AIProviderIcons.tsx +50 -0
  36. package/client/src/components/maps/GoogleMapsPicker.tsx +137 -0
  37. package/client/src/components/maps/MapsPreviewPanel.tsx +110 -0
  38. package/client/src/components/maps/index.ts +26 -0
  39. package/client/src/components/parameterPanel/InputSection.tsx +1094 -0
  40. package/client/src/components/parameterPanel/LocationPanelLayout.tsx +65 -0
  41. package/client/src/components/parameterPanel/MapsSection.tsx +92 -0
  42. package/client/src/components/parameterPanel/MiddleSection.tsx +571 -0
  43. package/client/src/components/parameterPanel/OutputSection.tsx +81 -0
  44. package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +82 -0
  45. package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +436 -0
  46. package/client/src/components/parameterPanel/index.ts +42 -0
  47. package/client/src/components/shared/DataPanel.tsx +142 -0
  48. package/client/src/components/shared/JSONTreeRenderer.tsx +106 -0
  49. package/client/src/components/ui/AIResultModal.tsx +204 -0
  50. package/client/src/components/ui/AndroidSettingsPanel.tsx +401 -0
  51. package/client/src/components/ui/CodeEditor.tsx +81 -0
  52. package/client/src/components/ui/CollapsibleSection.tsx +88 -0
  53. package/client/src/components/ui/ComponentItem.tsx +154 -0
  54. package/client/src/components/ui/ComponentPalette.tsx +321 -0
  55. package/client/src/components/ui/ConsolePanel.tsx +1074 -0
  56. package/client/src/components/ui/ErrorBoundary.tsx +196 -0
  57. package/client/src/components/ui/InputNodesPanel.tsx +204 -0
  58. package/client/src/components/ui/MapSelector.tsx +314 -0
  59. package/client/src/components/ui/Modal.tsx +149 -0
  60. package/client/src/components/ui/NodeContextMenu.tsx +192 -0
  61. package/client/src/components/ui/NodeOutputPanel.tsx +1150 -0
  62. package/client/src/components/ui/OutputDisplayPanel.tsx +381 -0
  63. package/client/src/components/ui/SettingsPanel.tsx +243 -0
  64. package/client/src/components/ui/TopToolbar.tsx +736 -0
  65. package/client/src/components/ui/WhatsAppSettingsPanel.tsx +345 -0
  66. package/client/src/components/ui/WorkflowSidebar.tsx +294 -0
  67. package/client/src/config/antdTheme.ts +186 -0
  68. package/client/src/config/api.ts +54 -0
  69. package/client/src/contexts/AuthContext.tsx +221 -0
  70. package/client/src/contexts/ThemeContext.tsx +42 -0
  71. package/client/src/contexts/WebSocketContext.tsx +1971 -0
  72. package/client/src/factories/baseChatModelFactory.ts +256 -0
  73. package/client/src/hooks/useAndroidOperations.ts +164 -0
  74. package/client/src/hooks/useApiKeyValidation.ts +107 -0
  75. package/client/src/hooks/useApiKeys.ts +238 -0
  76. package/client/src/hooks/useAppTheme.ts +17 -0
  77. package/client/src/hooks/useComponentPalette.ts +51 -0
  78. package/client/src/hooks/useCopyPaste.ts +155 -0
  79. package/client/src/hooks/useDragAndDrop.ts +124 -0
  80. package/client/src/hooks/useDragVariable.ts +88 -0
  81. package/client/src/hooks/useExecution.ts +313 -0
  82. package/client/src/hooks/useParameterPanel.ts +176 -0
  83. package/client/src/hooks/useReactFlowNodes.ts +189 -0
  84. package/client/src/hooks/useToolSchema.ts +209 -0
  85. package/client/src/hooks/useWhatsApp.ts +196 -0
  86. package/client/src/hooks/useWorkflowManagement.ts +46 -0
  87. package/client/src/index.css +315 -0
  88. package/client/src/main.tsx +19 -0
  89. package/client/src/nodeDefinitions/aiAgentNodes.ts +336 -0
  90. package/client/src/nodeDefinitions/aiModelNodes.ts +340 -0
  91. package/client/src/nodeDefinitions/androidDeviceNodes.ts +140 -0
  92. package/client/src/nodeDefinitions/androidServiceNodes.ts +383 -0
  93. package/client/src/nodeDefinitions/chatNodes.ts +135 -0
  94. package/client/src/nodeDefinitions/codeNodes.ts +54 -0
  95. package/client/src/nodeDefinitions/documentNodes.ts +379 -0
  96. package/client/src/nodeDefinitions/index.ts +15 -0
  97. package/client/src/nodeDefinitions/locationNodes.ts +463 -0
  98. package/client/src/nodeDefinitions/schedulerNodes.ts +220 -0
  99. package/client/src/nodeDefinitions/skillNodes.ts +211 -0
  100. package/client/src/nodeDefinitions/toolNodes.ts +198 -0
  101. package/client/src/nodeDefinitions/utilityNodes.ts +284 -0
  102. package/client/src/nodeDefinitions/whatsappNodes.ts +865 -0
  103. package/client/src/nodeDefinitions/workflowNodes.ts +41 -0
  104. package/client/src/nodeDefinitions.ts +104 -0
  105. package/client/src/schemas/workflowSchema.ts +264 -0
  106. package/client/src/services/dynamicParameterService.ts +96 -0
  107. package/client/src/services/execution/aiAgentExecutionService.ts +35 -0
  108. package/client/src/services/executionService.ts +232 -0
  109. package/client/src/services/workflowApi.ts +91 -0
  110. package/client/src/store/useAppStore.ts +582 -0
  111. package/client/src/styles/theme.ts +508 -0
  112. package/client/src/styles/zIndex.ts +17 -0
  113. package/client/src/types/ComponentTypes.ts +39 -0
  114. package/client/src/types/EdgeCondition.ts +231 -0
  115. package/client/src/types/INodeProperties.ts +288 -0
  116. package/client/src/types/NodeTypes.ts +28 -0
  117. package/client/src/utils/formatters.ts +33 -0
  118. package/client/src/utils/googleMapsLoader.ts +140 -0
  119. package/client/src/utils/locationUtils.ts +85 -0
  120. package/client/src/utils/nodeUtils.ts +31 -0
  121. package/client/src/utils/workflow.ts +30 -0
  122. package/client/src/utils/workflowExport.ts +120 -0
  123. package/client/src/vite-env.d.ts +12 -0
  124. package/client/tailwind.config.js +60 -0
  125. package/client/tsconfig.json +25 -0
  126. package/client/tsconfig.node.json +11 -0
  127. package/client/vite.config.js +35 -0
  128. package/docker-compose.prod.yml +107 -0
  129. package/docker-compose.yml +104 -0
  130. package/docs-MachinaOs/README.md +85 -0
  131. package/docs-MachinaOs/deployment/docker.mdx +228 -0
  132. package/docs-MachinaOs/deployment/production.mdx +345 -0
  133. package/docs-MachinaOs/docs.json +75 -0
  134. package/docs-MachinaOs/faq.mdx +309 -0
  135. package/docs-MachinaOs/favicon.svg +5 -0
  136. package/docs-MachinaOs/installation.mdx +160 -0
  137. package/docs-MachinaOs/introduction.mdx +114 -0
  138. package/docs-MachinaOs/logo/dark.svg +6 -0
  139. package/docs-MachinaOs/logo/light.svg +6 -0
  140. package/docs-MachinaOs/nodes/ai-agent.mdx +216 -0
  141. package/docs-MachinaOs/nodes/ai-models.mdx +240 -0
  142. package/docs-MachinaOs/nodes/android.mdx +411 -0
  143. package/docs-MachinaOs/nodes/overview.mdx +181 -0
  144. package/docs-MachinaOs/nodes/schedulers.mdx +316 -0
  145. package/docs-MachinaOs/nodes/webhooks.mdx +330 -0
  146. package/docs-MachinaOs/nodes/whatsapp.mdx +305 -0
  147. package/docs-MachinaOs/quickstart.mdx +119 -0
  148. package/docs-MachinaOs/tutorials/ai-agent-workflow.mdx +177 -0
  149. package/docs-MachinaOs/tutorials/android-automation.mdx +242 -0
  150. package/docs-MachinaOs/tutorials/first-workflow.mdx +134 -0
  151. package/docs-MachinaOs/tutorials/whatsapp-automation.mdx +185 -0
  152. package/nul +0 -0
  153. package/package.json +70 -0
  154. package/scripts/build.js +158 -0
  155. package/scripts/check-ports.ps1 +33 -0
  156. package/scripts/clean.js +40 -0
  157. package/scripts/docker.js +93 -0
  158. package/scripts/kill-port.ps1 +154 -0
  159. package/scripts/start.js +210 -0
  160. package/scripts/stop.js +325 -0
  161. package/server/.dockerignore +44 -0
  162. package/server/Dockerfile +45 -0
  163. package/server/constants.py +249 -0
  164. package/server/core/__init__.py +1 -0
  165. package/server/core/cache.py +461 -0
  166. package/server/core/config.py +128 -0
  167. package/server/core/container.py +99 -0
  168. package/server/core/database.py +1211 -0
  169. package/server/core/logging.py +314 -0
  170. package/server/main.py +289 -0
  171. package/server/middleware/__init__.py +5 -0
  172. package/server/middleware/auth.py +89 -0
  173. package/server/models/__init__.py +1 -0
  174. package/server/models/auth.py +52 -0
  175. package/server/models/cache.py +24 -0
  176. package/server/models/database.py +211 -0
  177. package/server/models/nodes.py +455 -0
  178. package/server/package.json +9 -0
  179. package/server/pyproject.toml +72 -0
  180. package/server/requirements.txt +83 -0
  181. package/server/routers/__init__.py +1 -0
  182. package/server/routers/android.py +294 -0
  183. package/server/routers/auth.py +203 -0
  184. package/server/routers/database.py +151 -0
  185. package/server/routers/maps.py +142 -0
  186. package/server/routers/nodejs_compat.py +289 -0
  187. package/server/routers/webhook.py +90 -0
  188. package/server/routers/websocket.py +2127 -0
  189. package/server/routers/whatsapp.py +761 -0
  190. package/server/routers/workflow.py +200 -0
  191. package/server/services/__init__.py +1 -0
  192. package/server/services/ai.py +2415 -0
  193. package/server/services/android/__init__.py +27 -0
  194. package/server/services/android/broadcaster.py +114 -0
  195. package/server/services/android/client.py +608 -0
  196. package/server/services/android/manager.py +78 -0
  197. package/server/services/android/protocol.py +165 -0
  198. package/server/services/android_service.py +588 -0
  199. package/server/services/auth.py +131 -0
  200. package/server/services/chat_client.py +160 -0
  201. package/server/services/deployment/__init__.py +12 -0
  202. package/server/services/deployment/manager.py +706 -0
  203. package/server/services/deployment/state.py +47 -0
  204. package/server/services/deployment/triggers.py +275 -0
  205. package/server/services/event_waiter.py +785 -0
  206. package/server/services/execution/__init__.py +77 -0
  207. package/server/services/execution/cache.py +769 -0
  208. package/server/services/execution/conditions.py +373 -0
  209. package/server/services/execution/dlq.py +132 -0
  210. package/server/services/execution/executor.py +1351 -0
  211. package/server/services/execution/models.py +531 -0
  212. package/server/services/execution/recovery.py +235 -0
  213. package/server/services/handlers/__init__.py +126 -0
  214. package/server/services/handlers/ai.py +355 -0
  215. package/server/services/handlers/android.py +260 -0
  216. package/server/services/handlers/code.py +278 -0
  217. package/server/services/handlers/document.py +598 -0
  218. package/server/services/handlers/http.py +193 -0
  219. package/server/services/handlers/polyglot.py +105 -0
  220. package/server/services/handlers/tools.py +845 -0
  221. package/server/services/handlers/triggers.py +107 -0
  222. package/server/services/handlers/utility.py +822 -0
  223. package/server/services/handlers/whatsapp.py +476 -0
  224. package/server/services/maps.py +289 -0
  225. package/server/services/memory_store.py +103 -0
  226. package/server/services/node_executor.py +375 -0
  227. package/server/services/parameter_resolver.py +218 -0
  228. package/server/services/polyglot_client.py +169 -0
  229. package/server/services/scheduler.py +155 -0
  230. package/server/services/skill_loader.py +417 -0
  231. package/server/services/status_broadcaster.py +826 -0
  232. package/server/services/temporal/__init__.py +23 -0
  233. package/server/services/temporal/activities.py +344 -0
  234. package/server/services/temporal/client.py +76 -0
  235. package/server/services/temporal/executor.py +147 -0
  236. package/server/services/temporal/worker.py +251 -0
  237. package/server/services/temporal/workflow.py +355 -0
  238. package/server/services/temporal/ws_client.py +236 -0
  239. package/server/services/text.py +111 -0
  240. package/server/services/user_auth.py +172 -0
  241. package/server/services/websocket_client.py +29 -0
  242. package/server/services/workflow.py +597 -0
  243. package/server/skills/android-skill/SKILL.md +82 -0
  244. package/server/skills/assistant-personality/SKILL.md +45 -0
  245. package/server/skills/code-skill/SKILL.md +140 -0
  246. package/server/skills/http-skill/SKILL.md +161 -0
  247. package/server/skills/maps-skill/SKILL.md +170 -0
  248. package/server/skills/memory-skill/SKILL.md +154 -0
  249. package/server/skills/scheduler-skill/SKILL.md +84 -0
  250. package/server/skills/whatsapp-skill/SKILL.md +283 -0
  251. package/server/uv.lock +2916 -0
  252. package/server/whatsapp-rpc/.dockerignore +30 -0
  253. package/server/whatsapp-rpc/Dockerfile +44 -0
  254. package/server/whatsapp-rpc/Dockerfile.web +17 -0
  255. package/server/whatsapp-rpc/README.md +139 -0
  256. package/server/whatsapp-rpc/cli.js +95 -0
  257. package/server/whatsapp-rpc/configs/config.yaml +7 -0
  258. package/server/whatsapp-rpc/docker-compose.yml +35 -0
  259. package/server/whatsapp-rpc/docs/API.md +410 -0
  260. package/server/whatsapp-rpc/go.mod +67 -0
  261. package/server/whatsapp-rpc/go.sum +203 -0
  262. package/server/whatsapp-rpc/package.json +30 -0
  263. package/server/whatsapp-rpc/schema.json +1294 -0
  264. package/server/whatsapp-rpc/scripts/clean.cjs +66 -0
  265. package/server/whatsapp-rpc/scripts/cli.js +162 -0
  266. package/server/whatsapp-rpc/src/go/cmd/server/main.go +91 -0
  267. package/server/whatsapp-rpc/src/go/config/config.go +49 -0
  268. package/server/whatsapp-rpc/src/go/rpc/rpc.go +446 -0
  269. package/server/whatsapp-rpc/src/go/rpc/server.go +112 -0
  270. package/server/whatsapp-rpc/src/go/whatsapp/history.go +166 -0
  271. package/server/whatsapp-rpc/src/go/whatsapp/messages.go +390 -0
  272. package/server/whatsapp-rpc/src/go/whatsapp/service.go +2130 -0
  273. package/server/whatsapp-rpc/src/go/whatsapp/types.go +261 -0
  274. package/server/whatsapp-rpc/src/python/pyproject.toml +15 -0
  275. package/server/whatsapp-rpc/src/python/whatsapp_rpc/__init__.py +4 -0
  276. package/server/whatsapp-rpc/src/python/whatsapp_rpc/client.py +427 -0
  277. package/server/whatsapp-rpc/web/app.py +609 -0
  278. package/server/whatsapp-rpc/web/requirements.txt +6 -0
  279. package/server/whatsapp-rpc/web/rpc_client.py +427 -0
  280. package/server/whatsapp-rpc/web/static/openapi.yaml +59 -0
  281. package/server/whatsapp-rpc/web/templates/base.html +150 -0
  282. package/server/whatsapp-rpc/web/templates/contacts.html +240 -0
  283. package/server/whatsapp-rpc/web/templates/dashboard.html +320 -0
  284. package/server/whatsapp-rpc/web/templates/groups.html +328 -0
  285. package/server/whatsapp-rpc/web/templates/messages.html +465 -0
  286. package/server/whatsapp-rpc/web/templates/messaging.html +681 -0
  287. package/server/whatsapp-rpc/web/templates/send.html +259 -0
  288. package/server/whatsapp-rpc/web/templates/settings.html +459 -0
@@ -0,0 +1,289 @@
1
+ """Google Maps service for location operations."""
2
+
3
+ import time
4
+ import googlemaps
5
+ from datetime import datetime
6
+ from typing import Dict, Any
7
+
8
+ from core.config import Settings
9
+ from core.logging import get_logger, log_execution_time
10
+ from services.auth import AuthService
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ class MapsService:
16
+ """Google Maps and Places API service."""
17
+
18
+ def __init__(self, auth_service: AuthService, settings: Settings):
19
+ self.auth = auth_service
20
+ self.settings = settings
21
+
22
+ def validate_coordinates(self, lat: float, lng: float) -> bool:
23
+ """Validate latitude and longitude."""
24
+ return -90 <= lat <= 90 and -180 <= lng <= 180
25
+
26
+ def validate_zoom_level(self, zoom: int) -> bool:
27
+ """Validate Google Maps zoom level."""
28
+ return 0 <= zoom <= 21
29
+
30
+ async def create_map(self, node_id: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
31
+ """Create Google Maps configuration."""
32
+ start_time = time.time()
33
+
34
+ try:
35
+ api_key = parameters.get('api_key') or self.settings.google_maps_api_key
36
+ if not api_key:
37
+ raise ValueError("Google Maps API key is required")
38
+
39
+ # Extract and validate parameters (snake_case)
40
+ lat = float(parameters.get('lat', 40.7128))
41
+ lng = float(parameters.get('lng', -74.0060))
42
+ zoom = int(parameters.get('zoom', 13))
43
+ map_type = parameters.get('map_type_id', 'ROADMAP')
44
+
45
+ if not self.validate_coordinates(lat, lng):
46
+ raise ValueError("Invalid coordinates")
47
+ if not self.validate_zoom_level(zoom):
48
+ raise ValueError("Invalid zoom level")
49
+ if map_type not in ['ROADMAP', 'SATELLITE', 'HYBRID', 'TERRAIN']:
50
+ raise ValueError("Invalid map type")
51
+
52
+ result = {
53
+ "map_config": {
54
+ "center": {"lat": lat, "lng": lng},
55
+ "zoom": zoom,
56
+ "mapTypeId": map_type
57
+ },
58
+ "static_map_url": f"https://maps.googleapis.com/maps/api/staticmap?center={lat},{lng}&zoom={zoom}&size=600x400&maptype={map_type.lower()}&key={api_key}",
59
+ "status": "OK"
60
+ }
61
+
62
+ log_execution_time(logger, "create_map", start_time, time.time())
63
+
64
+ return {
65
+ "success": True,
66
+ "node_id": node_id,
67
+ "node_type": "createMap",
68
+ "operation": "map_initialization",
69
+ "result": result,
70
+ "execution_time": time.time() - start_time,
71
+ "timestamp": datetime.now().isoformat()
72
+ }
73
+
74
+ except Exception as e:
75
+ logger.error("Create map failed", node_id=node_id, error=str(e))
76
+ return {
77
+ "success": False,
78
+ "node_id": node_id,
79
+ "node_type": "createMap",
80
+ "error": str(e),
81
+ "execution_time": time.time() - start_time,
82
+ "timestamp": datetime.now().isoformat()
83
+ }
84
+
85
+ async def geocode_location(self, node_id: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
86
+ """Geocode addresses or reverse geocode coordinates."""
87
+ start_time = time.time()
88
+
89
+ try:
90
+ api_key = parameters.get('api_key') or self.settings.google_maps_api_key
91
+ if not api_key:
92
+ raise ValueError("Google Maps API key is required")
93
+
94
+ gmaps = googlemaps.Client(key=api_key)
95
+ service_type = parameters.get('service_type', 'geocode')
96
+
97
+ if service_type == 'geocode':
98
+ address = parameters.get('address', '')
99
+ if not address:
100
+ raise ValueError("Address is required for geocoding")
101
+
102
+ geocode_result = gmaps.geocode(address=address)
103
+ result = {
104
+ "service_type": "geocoding",
105
+ "input": {"address": address},
106
+ "results": geocode_result,
107
+ "status": "OK" if geocode_result else "ZERO_RESULTS"
108
+ }
109
+
110
+ elif service_type == 'reverse_geocode':
111
+ lat = float(parameters.get('lat', 0))
112
+ lng = float(parameters.get('lng', 0))
113
+
114
+ if not self.validate_coordinates(lat, lng):
115
+ raise ValueError("Invalid coordinates")
116
+
117
+ reverse_result = gmaps.reverse_geocode((lat, lng))
118
+ result = {
119
+ "service_type": "reverse_geocoding",
120
+ "input": {"lat": lat, "lng": lng},
121
+ "results": reverse_result,
122
+ "status": "OK" if reverse_result else "ZERO_RESULTS"
123
+ }
124
+
125
+ else:
126
+ raise ValueError(f"Unsupported service type: {service_type}")
127
+
128
+ log_execution_time(logger, f"geocoding_{service_type}", start_time, time.time())
129
+
130
+ return {
131
+ "success": True,
132
+ "node_id": node_id,
133
+ "node_type": "addLocations",
134
+ "operation": service_type,
135
+ "result": result,
136
+ "execution_time": time.time() - start_time,
137
+ "timestamp": datetime.now().isoformat()
138
+ }
139
+
140
+ except googlemaps.exceptions.ApiError as e:
141
+ logger.error("Google Maps API error", node_id=node_id, error=str(e))
142
+ return {
143
+ "success": False,
144
+ "node_id": node_id,
145
+ "node_type": "addLocations",
146
+ "error": f"Google Maps API error: {str(e)}",
147
+ "execution_time": time.time() - start_time,
148
+ "timestamp": datetime.now().isoformat()
149
+ }
150
+
151
+ except Exception as e:
152
+ logger.error("Geocoding failed", node_id=node_id, error=str(e))
153
+ return {
154
+ "success": False,
155
+ "node_id": node_id,
156
+ "node_type": "addLocations",
157
+ "error": str(e),
158
+ "execution_time": time.time() - start_time,
159
+ "timestamp": datetime.now().isoformat()
160
+ }
161
+
162
+ async def find_nearby_places(self, node_id: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
163
+ """Find nearby places using Google Places API."""
164
+ start_time = time.time()
165
+
166
+ try:
167
+ api_key = parameters.get('api_key') or self.settings.google_maps_api_key
168
+ if not api_key:
169
+ raise ValueError("Google Maps API key is required")
170
+
171
+ gmaps = googlemaps.Client(key=api_key)
172
+
173
+ # Extract and validate parameters
174
+ lat = float(parameters.get('lat', 40.7484))
175
+ lng = float(parameters.get('lng', -73.9857))
176
+ radius = int(parameters.get('radius', 500))
177
+
178
+ if not self.validate_coordinates(lat, lng):
179
+ raise ValueError("Invalid coordinates")
180
+ if not (1 <= radius <= 50000):
181
+ raise ValueError("Radius must be between 1 and 50000 meters")
182
+
183
+ # Optional parameters (snake_case)
184
+ place_type = parameters.get('type', 'restaurant')
185
+ page_size = min(int(parameters.get('page_size', 20)), 20)
186
+
187
+ # Extract options (may contain keyword, name, min_price, max_price, open_now, language, rank_by)
188
+ options = parameters.get('options', {})
189
+
190
+ # Support both nested options and top-level parameters (snake_case)
191
+ keyword = options.get('keyword', '') if isinstance(options, dict) else parameters.get('keyword', '')
192
+ name_filter = options.get('name', '') if isinstance(options, dict) else parameters.get('name', '')
193
+ min_price = options.get('min_price') if isinstance(options, dict) else parameters.get('min_price')
194
+ max_price = options.get('max_price') if isinstance(options, dict) else parameters.get('max_price')
195
+ open_now = options.get('open_now', False) if isinstance(options, dict) else parameters.get('open_now', False)
196
+ language = options.get('language', 'en') if isinstance(options, dict) else parameters.get('language', 'en')
197
+ rank_by = options.get('rank_by', 'prominence') if isinstance(options, dict) else parameters.get('rank_by', 'prominence')
198
+
199
+ logger.debug("[Nearby Places] Extracted parameters",
200
+ keyword=keyword,
201
+ name_filter=name_filter,
202
+ min_price=min_price,
203
+ max_price=max_price,
204
+ open_now=open_now,
205
+ language=language,
206
+ rank_by=rank_by,
207
+ place_type=place_type)
208
+
209
+ # Build search request
210
+ search_params = {
211
+ 'location': (lat, lng),
212
+ 'type': place_type
213
+ }
214
+
215
+ # Add radius unless ranking by distance
216
+ if rank_by != 'distance':
217
+ search_params['radius'] = radius
218
+ else:
219
+ search_params['rank_by'] = rank_by
220
+
221
+ # Add optional parameters
222
+ if keyword:
223
+ search_params['keyword'] = keyword
224
+ if name_filter:
225
+ search_params['name'] = name_filter
226
+ if min_price is not None and min_price != '':
227
+ search_params['min_price'] = int(min_price)
228
+ if max_price is not None and max_price != '':
229
+ search_params['max_price'] = int(max_price)
230
+ if open_now:
231
+ search_params['open_now'] = True
232
+ if language:
233
+ search_params['language'] = language
234
+
235
+ nearby_result = gmaps.places_nearby(**search_params)
236
+ results = nearby_result.get('results', [])[:page_size]
237
+
238
+ result = {
239
+ "search_parameters": {
240
+ "location": {"lat": lat, "lng": lng},
241
+ "radius": radius if rank_by != 'distance' else None,
242
+ "type": place_type,
243
+ "keyword": keyword or None,
244
+ "name_filter": name_filter or None,
245
+ "min_price": min_price if min_price not in [None, ''] else None,
246
+ "max_price": max_price if max_price not in [None, ''] else None,
247
+ "open_now": open_now,
248
+ "language": language,
249
+ "rank_by": rank_by,
250
+ "page_size": page_size
251
+ },
252
+ "results": results,
253
+ "total_results": len(results),
254
+ "status": nearby_result.get('status', 'OK')
255
+ }
256
+
257
+ log_execution_time(logger, "nearby_places", start_time, time.time())
258
+
259
+ return {
260
+ "success": True,
261
+ "node_id": node_id,
262
+ "node_type": "showNearbyPlaces",
263
+ "operation": "nearby_search",
264
+ "result": result,
265
+ "execution_time": time.time() - start_time,
266
+ "timestamp": datetime.now().isoformat()
267
+ }
268
+
269
+ except googlemaps.exceptions.ApiError as e:
270
+ logger.error("Google Places API error", node_id=node_id, error=str(e))
271
+ return {
272
+ "success": False,
273
+ "node_id": node_id,
274
+ "node_type": "showNearbyPlaces",
275
+ "error": f"Google Places API error: {str(e)}",
276
+ "execution_time": time.time() - start_time,
277
+ "timestamp": datetime.now().isoformat()
278
+ }
279
+
280
+ except Exception as e:
281
+ logger.error("Nearby places search failed", node_id=node_id, error=str(e))
282
+ return {
283
+ "success": False,
284
+ "node_id": node_id,
285
+ "node_type": "showNearbyPlaces",
286
+ "error": str(e),
287
+ "execution_time": time.time() - start_time,
288
+ "timestamp": datetime.now().isoformat()
289
+ }
@@ -0,0 +1,103 @@
1
+ """Simple in-memory conversation storage for AI agents.
2
+
3
+ Uses standard Python data structures with LangChain message compatibility.
4
+ No deprecated APIs - follows LangChain 0.3+ recommendations.
5
+ """
6
+ from typing import Dict, List, Optional, Callable
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ @dataclass
15
+ class Message:
16
+ """Single conversation message."""
17
+ role: str # 'human' or 'ai'
18
+ content: str
19
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
20
+
21
+
22
+ @dataclass
23
+ class ConversationSession:
24
+ """Conversation session with message history."""
25
+ session_id: str
26
+ messages: List[Message] = field(default_factory=list)
27
+ created_at: str = field(default_factory=lambda: datetime.now().isoformat())
28
+
29
+
30
+ # Global in-memory store
31
+ _sessions: Dict[str, ConversationSession] = {}
32
+
33
+
34
+ def get_session(session_id: str) -> ConversationSession:
35
+ """Get or create a conversation session."""
36
+ if session_id not in _sessions:
37
+ _sessions[session_id] = ConversationSession(session_id=session_id)
38
+ logger.info(f"[Memory] Created new session: {session_id}")
39
+ return _sessions[session_id]
40
+
41
+
42
+ def add_message(session_id: str, role: str, content: str) -> None:
43
+ """Add a message to a session."""
44
+ session = get_session(session_id)
45
+ session.messages.append(Message(role=role, content=content))
46
+ logger.info(f"[Memory] Added {role} message to session '{session_id}' (total: {len(session.messages)})")
47
+
48
+
49
+ def get_messages(session_id: str, window_size: Optional[int] = None) -> List[Dict]:
50
+ """Get messages from a session, optionally limited to last N."""
51
+ session = get_session(session_id)
52
+ messages = session.messages
53
+ if window_size and window_size > 0:
54
+ messages = messages[-window_size:]
55
+ return [{"role": m.role, "content": m.content, "timestamp": m.timestamp} for m in messages]
56
+
57
+
58
+ def clear_session(session_id: str) -> int:
59
+ """Clear a session's messages. Returns count of cleared messages."""
60
+ if session_id in _sessions:
61
+ count = len(_sessions[session_id].messages)
62
+ _sessions[session_id].messages = []
63
+ logger.info(f"[Memory] Cleared {count} messages from session '{session_id}'")
64
+ return count
65
+ return 0
66
+
67
+
68
+ def delete_session(session_id: str) -> bool:
69
+ """Delete a session entirely. Returns True if session existed."""
70
+ if session_id in _sessions:
71
+ del _sessions[session_id]
72
+ logger.info(f"[Memory] Deleted session: {session_id}")
73
+ return True
74
+ return False
75
+
76
+
77
+ def get_all_sessions() -> List[Dict]:
78
+ """Get info about all sessions."""
79
+ return [
80
+ {
81
+ "session_id": s.session_id,
82
+ "message_count": len(s.messages),
83
+ "created_at": s.created_at
84
+ }
85
+ for s in _sessions.values()
86
+ ]
87
+
88
+
89
+ def get_langchain_messages(session_id: str, window_size: Optional[int] = None):
90
+ """Convert session messages to LangChain message format.
91
+
92
+ Uses langchain_core.messages (modern API, not deprecated).
93
+ """
94
+ from langchain_core.messages import HumanMessage, AIMessage
95
+
96
+ messages = get_messages(session_id, window_size)
97
+ lc_messages = []
98
+ for m in messages:
99
+ if m['role'] == 'human':
100
+ lc_messages.append(HumanMessage(content=m['content']))
101
+ elif m['role'] == 'ai':
102
+ lc_messages.append(AIMessage(content=m['content']))
103
+ return lc_messages