machinaos 0.0.64 → 0.0.73
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.
- package/client/components.json +25 -0
- package/client/dist/assets/ActionBar-vzPpSR77.js +1 -0
- package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +1 -0
- package/client/dist/assets/ApiKeyPanel-gfblELep.js +1 -0
- package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +1 -0
- package/client/dist/assets/EmailPanel-B1Om64p5.js +1 -0
- package/client/dist/assets/OAuthPanel-CXyQYGBz.js +1 -0
- package/client/dist/assets/QrPairingPanel-BgNuI1we.js +1 -0
- package/client/dist/assets/RateLimitSection-YYK8sx1T.js +1 -0
- package/client/dist/assets/StatusCard-DuYA5hJR.js +1 -0
- package/client/dist/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/client/dist/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/client/dist/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/client/dist/assets/index-D9tZfgvi.js +363 -0
- package/client/dist/assets/index-al7snTkG.css +1 -0
- package/client/dist/index.html +2 -2
- package/client/package.json +42 -8
- package/client/src/App.tsx +16 -12
- package/client/src/Dashboard.tsx +211 -266
- package/client/src/ParameterPanel.tsx +42 -124
- package/client/src/adapters/__tests__/nodeSpecToDescription.test.ts +352 -0
- package/client/src/adapters/nodeSpecToDescription.ts +349 -0
- package/client/src/assets/icons/NodeIcon.tsx +73 -0
- package/client/src/assets/icons/chat/chat.svg +1 -0
- package/client/src/assets/icons/code/javascript.svg +1 -0
- package/client/src/assets/icons/code/python.svg +1 -0
- package/client/src/assets/icons/code/typescript.svg +1 -0
- package/client/src/assets/icons/index.test.ts +74 -0
- package/client/src/assets/icons/index.ts +176 -0
- package/client/src/assets/icons/social/social.svg +1 -0
- package/client/src/assets/icons/twitter/x.svg +1 -0
- package/client/src/assets/icons/whatsapp/whatsapp-db.svg +1 -0
- package/client/src/assets/icons/whatsapp/whatsapp-receive.svg +1 -0
- package/client/src/assets/icons/whatsapp/whatsapp-send.svg +1 -0
- package/client/src/assets/icons/whatsapp/whatsapp.svg +1 -0
- package/client/src/components/AIAgentNode.tsx +160 -583
- package/client/src/components/APIKeyValidator.tsx +52 -54
- package/client/src/components/ConditionalEdge.tsx +24 -84
- package/client/src/components/CredentialsModal.tsx +1 -3151
- package/client/src/components/EdgeConditionEditor.tsx +162 -330
- package/client/src/components/GenericNode.tsx +40 -170
- package/client/src/components/LocationParameterPanel.tsx +17 -58
- package/client/src/components/OutputPanel.tsx +10 -8
- package/client/src/components/ParameterRenderer.tsx +161 -58
- package/client/src/components/PricingConfigModal.tsx +134 -159
- package/client/src/components/SkillEditorModal.tsx +320 -324
- package/client/src/components/SquareNode.tsx +122 -431
- package/client/src/components/StartNode.tsx +22 -100
- package/client/src/components/TeamMonitorNode.tsx +6 -4
- package/client/src/components/ToolkitNode.tsx +31 -134
- package/client/src/components/TriggerNode.tsx +38 -169
- package/client/src/components/__tests__/CredentialsModal.test.tsx +231 -0
- package/client/src/components/auth/LoginPage.tsx +75 -180
- package/client/src/components/auth/ProtectedRoute.tsx +4 -23
- package/client/src/components/credentials/CredentialsModal.tsx +118 -0
- package/client/src/components/credentials/CredentialsPalette.tsx +305 -0
- package/client/src/components/credentials/PanelRenderer.tsx +92 -0
- package/client/src/components/credentials/catalogueAdapter.ts +199 -0
- package/client/src/components/credentials/hooks.ts +15 -0
- package/client/src/components/credentials/index.ts +3 -0
- package/client/src/components/credentials/panels/ApiKeyPanel.tsx +116 -0
- package/client/src/components/credentials/panels/EmailPanel.tsx +371 -0
- package/client/src/components/credentials/panels/OAuthPanel.tsx +52 -0
- package/client/src/components/credentials/panels/QrPairingPanel.tsx +91 -0
- package/client/src/components/credentials/panels/schemas/email.ts +64 -0
- package/client/src/components/credentials/primitives/ActionBar.tsx +54 -0
- package/client/src/components/credentials/primitives/FieldRenderer.tsx +91 -0
- package/client/src/components/credentials/primitives/OAuthConnect.tsx +123 -0
- package/client/src/components/credentials/primitives/StatusCard.tsx +47 -0
- package/client/src/components/credentials/primitives/index.ts +5 -0
- package/client/src/components/credentials/providers.tsx +177 -0
- package/client/src/components/credentials/schema/extends.ts +168 -0
- package/client/src/components/credentials/sections/ApiUsageSection.tsx +120 -0
- package/client/src/components/credentials/sections/LlmUsageSection.tsx +146 -0
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +366 -0
- package/client/src/components/credentials/sections/RateLimitSection.tsx +306 -0
- package/client/src/components/credentials/sections/index.ts +4 -0
- package/client/src/components/credentials/types.ts +128 -0
- package/client/src/components/credentials/useCredentialPanel.ts +193 -0
- package/client/src/components/icons/AIProviderIcons.tsx +28 -14
- package/client/src/components/nodeMemoEquality.ts +28 -0
- package/client/src/components/onboarding/OnboardingWizard.tsx +49 -22
- package/client/src/components/onboarding/steps/ApiKeyStep.tsx +28 -49
- package/client/src/components/onboarding/steps/CanvasStep.tsx +61 -103
- package/client/src/components/onboarding/steps/ConceptsStep.tsx +69 -87
- package/client/src/components/onboarding/steps/GetStartedStep.tsx +82 -89
- package/client/src/components/onboarding/steps/WelcomeStep.tsx +27 -45
- package/client/src/components/output/OutputPanel.tsx +175 -0
- package/client/src/components/parameterPanel/InputSection.tsx +291 -1248
- package/client/src/components/parameterPanel/LocationPanelLayout.tsx +4 -4
- package/client/src/components/parameterPanel/MapsSection.tsx +1 -9
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +349 -370
- package/client/src/components/parameterPanel/MiddleSection.tsx +563 -846
- package/client/src/components/parameterPanel/OutputSection.tsx +34 -26
- package/client/src/components/parameterPanel/ParameterPanelLayout.tsx +4 -4
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +317 -335
- package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +200 -0
- package/client/src/components/parameterPanel/__tests__/MiddleSection.test.tsx +150 -0
- package/client/src/components/parameterPanel/__tests__/OutputSection.test.tsx +295 -0
- package/client/src/components/shared/DataPanel.tsx +26 -97
- package/client/src/components/shared/JSONTreeRenderer.tsx +105 -87
- package/client/src/components/ui/AIResultModal.tsx +39 -141
- package/client/src/components/ui/ApiKeyInput.tsx +40 -45
- package/client/src/components/ui/CodeEditor.tsx +6 -28
- package/client/src/components/ui/CollapsibleSection.tsx +25 -64
- package/client/src/components/ui/ComponentItem.tsx +35 -126
- package/client/src/components/ui/ComponentPalette.tsx +111 -230
- package/client/src/components/ui/ConsolePanel.tsx +446 -881
- package/client/src/components/ui/EditableNodeLabel.tsx +122 -0
- package/client/src/components/ui/ErrorBoundary.tsx +40 -151
- package/client/src/components/ui/InputNodesPanel.tsx +53 -100
- package/client/src/components/ui/Modal.tsx +83 -113
- package/client/src/components/ui/NodeContextMenu.tsx +39 -71
- package/client/src/components/ui/OutputDisplayPanel.tsx +180 -359
- package/client/src/components/ui/QRCodeDisplay.tsx +32 -64
- package/client/src/components/ui/SettingsPanel.tsx +248 -417
- package/client/src/components/ui/TopToolbar.tsx +248 -633
- package/client/src/components/ui/WorkflowSidebar.tsx +109 -252
- package/client/src/components/ui/accordion.tsx +79 -0
- package/client/src/components/ui/action-button.tsx +61 -0
- package/client/src/components/ui/alert-dialog.tsx +197 -0
- package/client/src/components/ui/alert.tsx +82 -0
- package/client/src/components/ui/badge.tsx +52 -0
- package/client/src/components/ui/button.tsx +67 -0
- package/client/src/components/ui/card.tsx +103 -0
- package/client/src/components/ui/checkbox.tsx +31 -0
- package/client/src/components/ui/collapsible.tsx +31 -0
- package/client/src/components/ui/dialog.tsx +168 -0
- package/client/src/components/ui/dropdown-menu.tsx +269 -0
- package/client/src/components/ui/form.tsx +167 -0
- package/client/src/components/ui/input.tsx +19 -0
- package/client/src/components/ui/label.tsx +24 -0
- package/client/src/components/ui/popover.tsx +89 -0
- package/client/src/components/ui/progress.tsx +29 -0
- package/client/src/components/ui/select.tsx +192 -0
- package/client/src/components/ui/settingsPanel/schema.ts +64 -0
- package/client/src/components/ui/skeleton.tsx +13 -0
- package/client/src/components/ui/slider.tsx +57 -0
- package/client/src/components/ui/sonner.tsx +48 -0
- package/client/src/components/ui/switch.tsx +31 -0
- package/client/src/components/ui/tabs.tsx +88 -0
- package/client/src/components/ui/textarea.tsx +18 -0
- package/client/src/components/ui/tooltip.tsx +55 -0
- package/client/src/contexts/WebSocketContext.tsx +566 -128
- package/client/src/hooks/__tests__/useApiKeys.test.ts +411 -0
- package/client/src/hooks/__tests__/useDragVariable.test.ts +223 -0
- package/client/src/hooks/useApiKeyValidation.ts +7 -7
- package/client/src/hooks/useAutoSkillEdges.ts +132 -0
- package/client/src/hooks/useCatalogueQuery.ts +314 -0
- package/client/src/hooks/useComponentPalette.ts +4 -3
- package/client/src/hooks/useCopyPaste.ts +2 -3
- package/client/src/hooks/useDragAndDrop.ts +14 -7
- package/client/src/hooks/useDragVariable.ts +2 -2
- package/client/src/hooks/useExecution.ts +7 -7
- package/client/src/hooks/useFolderSkills.ts +73 -0
- package/client/src/hooks/useNodeAllowlist.ts +49 -0
- package/client/src/hooks/useNodeParamsQuery.ts +66 -0
- package/client/src/hooks/useOnboarding.ts +45 -47
- package/client/src/hooks/useParameterPanel.ts +89 -111
- package/client/src/hooks/useReactFlowNodes.ts +3 -3
- package/client/src/hooks/useUserSettingsQuery.ts +53 -0
- package/client/src/hooks/useWorkflowManagement.ts +5 -3
- package/client/src/hooks/useWorkflowsQuery.ts +96 -0
- package/client/src/index.css +450 -215
- package/client/src/lib/__tests__/workflowOps.test.ts +435 -0
- package/client/src/lib/aiModelProviders.ts +34 -0
- package/client/src/lib/featureFlags.ts +36 -0
- package/client/src/lib/nodeSpec.ts +327 -0
- package/client/src/lib/queryClient.ts +72 -0
- package/client/src/lib/queryConfig.ts +81 -0
- package/client/src/lib/queryPersist.ts +79 -0
- package/client/src/lib/utils.ts +6 -0
- package/client/src/lib/workflowOps.ts +291 -0
- package/client/src/main.tsx +29 -7
- package/client/src/services/executionService.ts +25 -83
- package/client/src/store/useAppStore.ts +12 -58
- package/client/src/store/useCredentialRegistry.ts +45 -0
- package/client/src/stores/nodeStatusStore.ts +134 -0
- package/client/src/styles/canvasAnimations.ts +112 -0
- package/client/src/styles/theme.ts +68 -1
- package/client/src/test/README.md +34 -0
- package/client/src/test/builders.ts +90 -0
- package/client/src/test/nodeDefinitionContract.ts +182 -0
- package/client/src/test/providers.tsx +80 -0
- package/client/src/test/setup.ts +62 -0
- package/client/src/types/ComponentTypes.ts +3 -1
- package/client/src/types/INodeProperties.ts +51 -2
- package/client/src/utils/formatters.ts +2 -2
- package/client/src/utils/parameterVisibility.ts +29 -0
- package/client/tailwind.config.js +39 -2
- package/client/tsconfig.json +5 -1
- package/client/vite.config.js +59 -1
- package/client/vitest.config.ts +29 -0
- package/package.json +20 -23
- package/scripts/migrate_icons.py +143 -0
- package/scripts/migrate_skill_icons.py +186 -0
- package/scripts/postinstall.js +25 -5
- package/server/config/credential_providers.json +400 -0
- package/server/config/llm_defaults.json +44 -24
- package/server/config/model_registry.json +1292 -541
- package/server/config/node_allowlist.json +29 -0
- package/server/core/config.py +13 -2
- package/server/core/database.py +35 -5
- package/server/core/logging.py +1 -1
- package/server/core/tracing.py +82 -0
- package/server/main.py +34 -3
- package/server/models/database.py +1 -0
- package/server/models/node_metadata.py +109 -0
- package/server/nodes/README.md +351 -0
- package/server/nodes/__init__.py +113 -0
- package/server/nodes/_visuals.py +81 -0
- package/server/nodes/agent/__init__.py +1 -0
- package/server/nodes/agent/_handles.py +55 -0
- package/server/nodes/agent/_inline.py +154 -0
- package/server/nodes/agent/_specialized.py +125 -0
- package/server/nodes/agent/ai_agent.py +132 -0
- package/server/nodes/agent/ai_employee.py +11 -0
- package/server/nodes/agent/android_agent.py +9 -0
- package/server/nodes/agent/autonomous_agent.py +9 -0
- package/server/nodes/agent/chat_agent.py +118 -0
- package/server/nodes/agent/claude_code_agent.py +133 -0
- package/server/nodes/agent/coding_agent.py +9 -0
- package/server/nodes/agent/consumer_agent.py +9 -0
- package/server/nodes/agent/deep_agent.py +103 -0
- package/server/nodes/agent/orchestrator_agent.py +11 -0
- package/server/nodes/agent/payments_agent.py +9 -0
- package/server/nodes/agent/productivity_agent.py +9 -0
- package/server/nodes/agent/rlm_agent.py +98 -0
- package/server/nodes/agent/social_agent.py +9 -0
- package/server/nodes/agent/task_agent.py +9 -0
- package/server/nodes/agent/tool_agent.py +9 -0
- package/server/nodes/agent/travel_agent.py +9 -0
- package/server/nodes/agent/web_agent.py +9 -0
- package/server/nodes/android/__init__.py +1 -0
- package/server/nodes/android/_base.py +330 -0
- package/server/nodes/android/airplane_mode_control.py +7 -0
- package/server/nodes/android/app_launcher.py +29 -0
- package/server/nodes/android/app_list.py +7 -0
- package/server/nodes/android/audio_automation.py +7 -0
- package/server/nodes/android/battery_monitor.py +7 -0
- package/server/nodes/android/bluetooth_automation.py +7 -0
- package/server/nodes/android/camera_control.py +7 -0
- package/server/nodes/android/device_state_automation.py +7 -0
- package/server/nodes/android/environmental_sensors.py +7 -0
- package/server/nodes/android/location.py +7 -0
- package/server/nodes/android/media_control.py +7 -0
- package/server/nodes/android/motion_detection.py +7 -0
- package/server/nodes/android/network_monitor.py +7 -0
- package/server/nodes/android/screen_control_automation.py +7 -0
- package/server/nodes/android/system_info.py +7 -0
- package/server/nodes/android/wifi_automation.py +7 -0
- package/server/nodes/browser/__init__.py +1 -0
- package/server/nodes/browser/browser.py +364 -0
- package/server/nodes/chat/__init__.py +1 -0
- package/server/nodes/chat/chat_history.py +67 -0
- package/server/nodes/chat/chat_send.py +79 -0
- package/server/nodes/code/__init__.py +1 -0
- package/server/nodes/code/_base.py +47 -0
- package/server/nodes/code/_nodejs.py +22 -0
- package/server/nodes/code/javascript_executor.py +57 -0
- package/server/nodes/code/python_executor.py +123 -0
- package/server/nodes/code/typescript_executor.py +53 -0
- package/server/nodes/document/__init__.py +1 -0
- package/server/nodes/document/document_parser.py +115 -0
- package/server/nodes/document/embedding_generator.py +110 -0
- package/server/nodes/document/file_downloader.py +111 -0
- package/server/nodes/document/http_scraper.py +193 -0
- package/server/nodes/document/text_chunker.py +77 -0
- package/server/nodes/document/vector_store.py +279 -0
- package/server/nodes/email/__init__.py +1 -0
- package/server/nodes/email/email_read.py +124 -0
- package/server/nodes/email/email_receive.py +141 -0
- package/server/nodes/email/email_send.py +58 -0
- package/server/nodes/filesystem/__init__.py +1 -0
- package/server/nodes/filesystem/_backend.py +189 -0
- package/server/nodes/filesystem/file_modify.py +101 -0
- package/server/nodes/filesystem/file_read.py +64 -0
- package/server/nodes/filesystem/fs_search.py +90 -0
- package/server/nodes/filesystem/shell.py +83 -0
- package/server/nodes/google/__init__.py +1 -0
- package/server/nodes/google/_base.py +84 -0
- package/server/nodes/google/_credentials.py +61 -0
- package/server/nodes/google/_gmail.py +106 -0
- package/server/nodes/google/calendar.py +238 -0
- package/server/nodes/google/contacts.py +243 -0
- package/server/nodes/google/drive.py +272 -0
- package/server/nodes/google/gmail.py +196 -0
- package/server/nodes/google/gmail_receive.py +172 -0
- package/server/nodes/google/sheets.py +174 -0
- package/server/nodes/google/tasks.py +175 -0
- package/server/nodes/groups.py +57 -0
- package/server/nodes/location/__init__.py +1 -0
- package/server/nodes/location/_credentials.py +21 -0
- package/server/nodes/location/gmaps_create.py +72 -0
- package/server/nodes/location/gmaps_locations.py +83 -0
- package/server/nodes/location/gmaps_nearby_places.py +62 -0
- package/server/nodes/model/__init__.py +1 -0
- package/server/nodes/model/_base.py +108 -0
- package/server/nodes/model/_credentials.py +97 -0
- package/server/nodes/model/anthropic_chat_model.py +27 -0
- package/server/nodes/model/cerebras_chat_model.py +26 -0
- package/server/nodes/model/deepseek_chat_model.py +27 -0
- package/server/nodes/model/gemini_chat_model.py +28 -0
- package/server/nodes/model/groq_chat_model.py +26 -0
- package/server/nodes/model/kimi_chat_model.py +12 -0
- package/server/nodes/model/mistral_chat_model.py +12 -0
- package/server/nodes/model/openai_chat_model.py +35 -0
- package/server/nodes/model/openrouter_chat_model.py +27 -0
- package/server/nodes/proxy/__init__.py +1 -0
- package/server/nodes/proxy/_usage.py +52 -0
- package/server/nodes/proxy/proxy_config.py +388 -0
- package/server/nodes/proxy/proxy_request.py +168 -0
- package/server/nodes/proxy/proxy_status.py +55 -0
- package/server/nodes/scheduler/__init__.py +1 -0
- package/server/nodes/scheduler/cron_scheduler.py +239 -0
- package/server/nodes/scheduler/timer.py +92 -0
- package/server/nodes/scraper/__init__.py +1 -0
- package/server/nodes/scraper/_credentials.py +19 -0
- package/server/nodes/scraper/apify_actor.py +314 -0
- package/server/nodes/scraper/crawlee_scraper.py +347 -0
- package/server/nodes/search/__init__.py +1 -0
- package/server/nodes/search/brave_search.py +124 -0
- package/server/nodes/search/duckduckgo_search.py +76 -0
- package/server/nodes/search/perplexity_search.py +128 -0
- package/server/nodes/search/serper_search.py +145 -0
- package/server/nodes/skill/__init__.py +1 -0
- package/server/nodes/skill/master_skill.py +64 -0
- package/server/nodes/skill/simple_memory.py +123 -0
- package/server/nodes/social/__init__.py +1 -0
- package/server/{services/handlers/social.py → nodes/social/_base.py} +33 -28
- package/server/nodes/social/social_receive.py +156 -0
- package/server/nodes/social/social_send.py +371 -0
- package/server/nodes/telegram/__init__.py +65 -0
- package/server/nodes/telegram/_credentials.py +19 -0
- package/server/nodes/telegram/_filters.py +122 -0
- package/server/nodes/telegram/_handlers.py +193 -0
- package/server/nodes/telegram/_refresh.py +106 -0
- package/server/{services/telegram_service.py → nodes/telegram/_service.py} +314 -272
- package/server/nodes/telegram/telegram_receive.py +149 -0
- package/server/nodes/telegram/telegram_send.py +269 -0
- package/server/nodes/text/__init__.py +1 -0
- package/server/nodes/text/file_handler.py +80 -0
- package/server/nodes/text/text_generator.py +55 -0
- package/server/nodes/tool/__init__.py +1 -0
- package/server/nodes/tool/calculator_tool.py +85 -0
- package/server/nodes/tool/current_time_tool.py +59 -0
- package/server/nodes/tool/task_manager.py +55 -0
- package/server/nodes/tool/write_todos.py +77 -0
- package/server/nodes/trigger/__init__.py +1 -0
- package/server/nodes/trigger/chat_trigger.py +71 -0
- package/server/nodes/trigger/task_trigger.py +80 -0
- package/server/nodes/trigger/webhook_trigger.py +110 -0
- package/server/nodes/twitter/__init__.py +1 -0
- package/server/nodes/twitter/_base.py +311 -0
- package/server/nodes/twitter/_credentials.py +35 -0
- package/server/nodes/twitter/twitter_receive.py +68 -0
- package/server/nodes/twitter/twitter_search.py +109 -0
- package/server/nodes/twitter/twitter_send.py +193 -0
- package/server/nodes/twitter/twitter_user.py +151 -0
- package/server/nodes/utility/__init__.py +1 -0
- package/server/nodes/utility/console.py +208 -0
- package/server/nodes/utility/http_request.py +164 -0
- package/server/nodes/utility/process_manager.py +111 -0
- package/server/nodes/utility/team_monitor.py +81 -0
- package/server/nodes/utility/webhook_response.py +85 -0
- package/server/nodes/visuals.json +471 -0
- package/server/nodes/whatsapp/__init__.py +24 -0
- package/server/{services/handlers/whatsapp.py → nodes/whatsapp/_base.py} +9 -3
- package/server/nodes/whatsapp/_runtime.py +116 -0
- package/server/nodes/whatsapp/whatsapp_db.py +348 -0
- package/server/nodes/whatsapp/whatsapp_receive.py +121 -0
- package/server/nodes/whatsapp/whatsapp_send.py +270 -0
- package/server/nodes/workflow/__init__.py +1 -0
- package/server/nodes/workflow/start.py +64 -0
- package/server/package-lock.json +1644 -0
- package/server/package.json +6 -1
- package/server/pyproject.toml +49 -9
- package/server/routers/credentials.py +88 -0
- package/server/routers/schemas.py +128 -0
- package/server/routers/websocket.py +527 -298
- package/server/services/_supervisor/__init__.py +36 -0
- package/server/services/_supervisor/base.py +153 -0
- package/server/services/_supervisor/client.py +55 -0
- package/server/services/_supervisor/process.py +125 -0
- package/server/services/_supervisor/registry.py +63 -0
- package/server/services/_supervisor/util.py +114 -0
- package/server/services/agents/adapters.py +18 -23
- package/server/services/agents/service.py +12 -9
- package/server/services/ai.py +336 -1220
- package/server/services/auth.py +86 -0
- package/server/services/auto_skill.py +152 -0
- package/server/services/browser_service.py +2 -31
- package/server/services/circuit_breaker.py +258 -0
- package/server/services/compaction.py +16 -17
- package/server/services/credential_registry.py +271 -0
- package/server/services/deployment/manager.py +13 -11
- package/server/services/deployment/triggers.py +6 -6
- package/server/services/event_waiter.py +146 -133
- package/server/services/google_oauth.py +22 -0
- package/server/services/handlers/__init__.py +22 -228
- package/server/services/handlers/todo.py +41 -49
- package/server/services/handlers/tools.py +235 -1814
- package/server/services/handlers/triggers.py +14 -18
- package/server/services/idempotency.py +198 -0
- package/server/services/llm/config.py +1 -1
- package/server/services/memory.py +66 -0
- package/server/services/model_registry.py +10 -0
- package/server/services/node_allowlist.py +68 -0
- package/server/services/node_executor.py +72 -175
- package/server/services/node_input_schemas.py +96 -0
- package/server/services/node_option_loaders/__init__.py +77 -0
- package/server/services/node_option_loaders/android_loaders.py +55 -0
- package/server/services/node_option_loaders/google_loaders.py +97 -0
- package/server/services/node_option_loaders/whatsapp_loaders.py +69 -0
- package/server/services/node_output_schemas.py +889 -0
- package/server/services/node_registry.py +129 -0
- package/server/services/node_spec.py +180 -0
- package/server/services/parameter_resolver.py +17 -7
- package/server/services/plugin/__init__.py +74 -0
- package/server/services/plugin/action.py +21 -0
- package/server/services/plugin/base.py +439 -0
- package/server/services/plugin/connection.py +136 -0
- package/server/services/plugin/context.py +80 -0
- package/server/services/plugin/credential.py +175 -0
- package/server/services/plugin/edge_walker.py +448 -0
- package/server/services/plugin/interceptor.py +70 -0
- package/server/services/plugin/operation.py +88 -0
- package/server/services/plugin/routing.py +186 -0
- package/server/services/plugin/scaling.py +78 -0
- package/server/services/plugin/tool.py +51 -0
- package/server/services/plugin/trigger.py +119 -0
- package/server/services/process_service.py +25 -28
- package/server/services/proxy/service.py +10 -7
- package/server/services/rlm/adapters.py +1 -1
- package/server/services/rlm/service.py +7 -7
- package/server/services/skill_loader.py +28 -6
- package/server/services/status_broadcaster.py +363 -186
- package/server/services/temporal/plugin_activities.py +109 -0
- package/server/services/temporal/worker.py +150 -34
- package/server/services/temporal/workflow.py +4 -7
- package/server/services/text.py +3 -3
- package/server/services/todo_service.py +2 -1
- package/server/{routers/whatsapp.py → services/whatsapp_service.py} +11 -0
- package/server/services/workflow.py +50 -14
- package/server/services/workflow_ops.py +241 -0
- package/server/services/ws_handler_registry.py +61 -0
- package/server/skills/GUIDE.md +2 -4
- package/server/skills/android_agent/app-launcher-skill/SKILL.md +1 -2
- package/server/skills/android_agent/app-list-skill/SKILL.md +1 -2
- package/server/skills/android_agent/audio-skill/SKILL.md +1 -2
- package/server/skills/android_agent/battery-skill/SKILL.md +1 -2
- package/server/skills/android_agent/bluetooth-skill/SKILL.md +1 -2
- package/server/skills/android_agent/camera-skill/SKILL.md +1 -2
- package/server/skills/android_agent/environmental-skill/SKILL.md +1 -2
- package/server/skills/android_agent/location-skill/SKILL.md +1 -2
- package/server/skills/android_agent/motion-skill/SKILL.md +1 -2
- package/server/skills/android_agent/personality/SKILL.md +1 -0
- package/server/skills/android_agent/screen-control-skill/SKILL.md +1 -2
- package/server/skills/android_agent/wifi-skill/SKILL.md +1 -2
- package/server/skills/assistant/assistant-personality/SKILL.md +1 -0
- package/server/skills/assistant/compaction-skill/SKILL.md +1 -0
- package/server/skills/assistant/humanify-skill/SKILL.md +1 -0
- package/server/skills/assistant/memory-skill/SKILL.md +1 -0
- package/server/skills/assistant/subagent-skill/SKILL.md +1 -0
- package/server/skills/autonomous/agentic-loop-skill/SKILL.md +2 -3
- package/server/skills/autonomous/code-mode-skill/SKILL.md +2 -3
- package/server/skills/autonomous/error-recovery-skill/SKILL.md +2 -3
- package/server/skills/autonomous/multi-tool-orchestration-skill/SKILL.md +2 -3
- package/server/skills/autonomous/progressive-discovery-skill/SKILL.md +1 -0
- package/server/skills/coding_agent/file-modify-skill/SKILL.md +8 -9
- package/server/skills/coding_agent/file-read-skill/SKILL.md +1 -2
- package/server/skills/coding_agent/fs-search-skill/SKILL.md +1 -2
- package/server/skills/coding_agent/javascript-skill/SKILL.md +2 -3
- package/server/skills/coding_agent/python-skill/SKILL.md +19 -18
- package/server/skills/productivity_agent/{calendar-skill → google-calendar-skill}/SKILL.md +3 -4
- package/server/skills/productivity_agent/{contacts-skill → google-contacts-skill}/SKILL.md +3 -4
- package/server/skills/productivity_agent/{drive-skill → google-drive-skill}/SKILL.md +3 -4
- package/server/skills/productivity_agent/{gmail-skill → google-gmail-skill}/SKILL.md +3 -4
- package/server/skills/productivity_agent/{sheets-skill → google-sheets-skill}/SKILL.md +3 -4
- package/server/skills/productivity_agent/{tasks-skill → google-tasks-skill}/SKILL.md +3 -4
- package/server/skills/rlm_agent/rlm-reasoning-skill/SKILL.md +2 -1
- package/server/skills/social_agent/twitter-search-skill/SKILL.md +44 -10
- package/server/skills/social_agent/twitter-send-skill/SKILL.md +1 -2
- package/server/skills/social_agent/twitter-user-skill/SKILL.md +1 -2
- package/server/skills/social_agent/whatsapp-db-skill/SKILL.md +1 -2
- package/server/skills/social_agent/whatsapp-send-skill/SKILL.md +1 -2
- package/server/skills/task_agent/cron-scheduler-skill/SKILL.md +1 -2
- package/server/skills/task_agent/task-manager-skill/SKILL.md +1 -2
- package/server/skills/task_agent/timer-skill/SKILL.md +1 -2
- package/server/skills/task_agent/write-todos-skill/SKILL.md +1 -2
- package/server/skills/terminal/bash-skill/SKILL.md +2 -3
- package/server/skills/terminal/powershell-skill/SKILL.md +2 -3
- package/server/skills/terminal/process-manager-skill/SKILL.md +1 -2
- package/server/skills/terminal/shell-skill/SKILL.md +107 -70
- package/server/skills/terminal/wsl-skill/SKILL.md +2 -3
- package/server/skills/travel_agent/geocoding-skill/SKILL.md +2 -3
- package/server/skills/travel_agent/nearby-places-skill/SKILL.md +2 -3
- package/server/skills/web_agent/apify-skill/SKILL.md +2 -3
- package/server/skills/web_agent/brave-search-skill/SKILL.md +1 -2
- package/server/skills/web_agent/browser-skill/SKILL.md +1 -2
- package/server/skills/web_agent/crawlee-scraper-skill/SKILL.md +2 -3
- package/server/skills/web_agent/duckduckgo-search-skill/SKILL.md +2 -3
- package/server/skills/web_agent/http-request-skill/SKILL.md +1 -2
- package/server/skills/web_agent/perplexity-search-skill/SKILL.md +1 -2
- package/server/skills/web_agent/proxy-config-skill/SKILL.md +2 -3
- package/server/skills/web_agent/serper-search-skill/SKILL.md +1 -2
- package/server/tests/conftest.py +144 -9
- package/server/tests/credentials/README.md +41 -0
- package/server/tests/credentials/__init__.py +0 -0
- package/server/tests/credentials/conftest.py +106 -0
- package/server/tests/credentials/test_auth_service.py +175 -0
- package/server/tests/credentials/test_credentials_database.py +223 -0
- package/server/tests/credentials/test_encryption.py +107 -0
- package/server/tests/credentials/test_google_oauth.py +116 -0
- package/server/tests/credentials/test_oauth_utils.py +89 -0
- package/server/tests/credentials/test_twitter_oauth.py +275 -0
- package/server/tests/credentials/test_websocket_handlers.py +293 -0
- package/server/tests/llm/test_factory.py +4 -1
- package/server/tests/llm/test_wiring.py +2 -2
- package/server/tests/nodes/__init__.py +6 -0
- package/server/tests/nodes/_compat.py +168 -0
- package/server/tests/nodes/_harness.py +274 -0
- package/server/tests/nodes/_mocks.py +235 -0
- package/server/tests/nodes/test_ai_agents.py +478 -0
- package/server/tests/nodes/test_ai_chat_models.py +383 -0
- package/server/tests/nodes/test_ai_tools.py +481 -0
- package/server/tests/nodes/test_android.py +463 -0
- package/server/tests/nodes/test_chat_utility.py +496 -0
- package/server/tests/nodes/test_code_fs_process.py +777 -0
- package/server/tests/nodes/test_document.py +591 -0
- package/server/tests/nodes/test_email.py +311 -0
- package/server/tests/nodes/test_google_workspace.py +764 -0
- package/server/tests/nodes/test_http_proxy.py +399 -0
- package/server/tests/nodes/test_search.py +327 -0
- package/server/tests/nodes/test_specialized_agents.py +590 -0
- package/server/tests/nodes/test_telegram_social.py +596 -0
- package/server/tests/nodes/test_twitter.py +659 -0
- package/server/tests/nodes/test_web_automation.py +461 -0
- package/server/tests/nodes/test_whatsapp.py +437 -0
- package/server/tests/nodes/test_workflow_triggers.py +492 -0
- package/server/tests/services/__init__.py +0 -0
- package/server/tests/services/test_supervisor.py +286 -0
- package/server/tests/test_auto_skill.py +306 -0
- package/server/tests/test_node_spec.py +1020 -0
- package/server/tests/test_parameter_resolver.py +109 -0
- package/server/tests/test_plugin_contract.py +208 -0
- package/server/tests/test_workflow_ops.py +230 -0
- package/workflows/zeenie-agent.json +2 -2
- package/client/dist/assets/index-C2IQ3pBs.js +0 -900
- package/client/dist/assets/index-DFSC53FP.css +0 -1
- package/client/src/components/ClaudeChatModelNode.tsx +0 -18
- package/client/src/components/GeminiChatModelNode.tsx +0 -18
- package/client/src/components/ModelNode.tsx +0 -282
- package/client/src/components/OpenAIChatModelNode.tsx +0 -18
- package/client/src/components/base/BaseChatModelNode.tsx +0 -271
- package/client/src/components/ui/NodeOutputPanel.tsx +0 -1512
- package/client/src/config/antdTheme.ts +0 -186
- package/client/src/factories/baseChatModelFactory.ts +0 -275
- package/client/src/nodeDefinitions/aiAgentNodes.ts +0 -382
- package/client/src/nodeDefinitions/aiModelNodes.ts +0 -281
- package/client/src/nodeDefinitions/androidServiceNodes.ts +0 -383
- package/client/src/nodeDefinitions/apifyNodes.ts +0 -253
- package/client/src/nodeDefinitions/browserNodes.ts +0 -306
- package/client/src/nodeDefinitions/chatNodes.ts +0 -135
- package/client/src/nodeDefinitions/codeNodes.ts +0 -78
- package/client/src/nodeDefinitions/crawleeNodes.ts +0 -254
- package/client/src/nodeDefinitions/documentNodes.ts +0 -404
- package/client/src/nodeDefinitions/emailNodes.ts +0 -120
- package/client/src/nodeDefinitions/filesystemNodes.ts +0 -208
- package/client/src/nodeDefinitions/googleWorkspaceNodes.ts +0 -1529
- package/client/src/nodeDefinitions/index.ts +0 -15
- package/client/src/nodeDefinitions/locationNodes.ts +0 -463
- package/client/src/nodeDefinitions/processNodes.ts +0 -104
- package/client/src/nodeDefinitions/proxyNodes.ts +0 -358
- package/client/src/nodeDefinitions/schedulerNodes.ts +0 -236
- package/client/src/nodeDefinitions/searchNodes.ts +0 -323
- package/client/src/nodeDefinitions/skillNodes.ts +0 -56
- package/client/src/nodeDefinitions/socialNodes.ts +0 -750
- package/client/src/nodeDefinitions/specializedAgentNodes.ts +0 -564
- package/client/src/nodeDefinitions/telegramNodes.ts +0 -350
- package/client/src/nodeDefinitions/toolNodes.ts +0 -276
- package/client/src/nodeDefinitions/twitterNodes.ts +0 -441
- package/client/src/nodeDefinitions/utilityNodes.ts +0 -364
- package/client/src/nodeDefinitions/whatsappNodes.ts +0 -1145
- package/client/src/nodeDefinitions/workflowNodes.ts +0 -95
- package/client/src/nodeDefinitions.ts +0 -179
- package/scripts/build.js +0 -208
- package/scripts/clean.js +0 -77
- package/scripts/daemon.js +0 -451
- package/scripts/dev.js +0 -138
- package/scripts/port_kill.py +0 -189
- package/scripts/start.js +0 -205
- package/scripts/stop.js +0 -61
- package/scripts/sync-version.js +0 -108
- package/scripts/utils.js +0 -352
- package/server/models/nodes.py +0 -511
- package/server/services/handlers/ai.py +0 -628
- package/server/services/handlers/android.py +0 -88
- package/server/services/handlers/apify.py +0 -261
- package/server/services/handlers/browser.py +0 -212
- package/server/services/handlers/calendar.py +0 -448
- package/server/services/handlers/claude_code.py +0 -97
- package/server/services/handlers/code.py +0 -320
- package/server/services/handlers/contacts.py +0 -537
- package/server/services/handlers/crawlee.py +0 -324
- package/server/services/handlers/deep_agent.py +0 -84
- package/server/services/handlers/document.py +0 -625
- package/server/services/handlers/drive.py +0 -465
- package/server/services/handlers/email.py +0 -86
- package/server/services/handlers/filesystem.py +0 -169
- package/server/services/handlers/gmail.py +0 -589
- package/server/services/handlers/http.py +0 -221
- package/server/services/handlers/polyglot.py +0 -105
- package/server/services/handlers/process.py +0 -83
- package/server/services/handlers/proxy.py +0 -326
- package/server/services/handlers/rlm.py +0 -78
- package/server/services/handlers/search.py +0 -385
- package/server/services/handlers/sheets.py +0 -314
- package/server/services/handlers/tasks.py +0 -452
- package/server/services/handlers/telegram.py +0 -244
- package/server/services/handlers/twitter.py +0 -652
- package/server/services/handlers/utility.py +0 -913
- package/server/services/polyglot_client.py +0 -169
- package/workflows/ai-employee-experimental.json +0 -1400
- package/workflows/zeenie-assistant.json +0 -1871
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
"""Search API node handlers - Brave Search, Serper, Perplexity Sonar.
|
|
2
|
-
|
|
3
|
-
Each handler fetches the API key from the encrypted credentials system
|
|
4
|
-
via auth_service, then calls the provider's API.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import time
|
|
8
|
-
from typing import Dict, Any
|
|
9
|
-
|
|
10
|
-
import httpx
|
|
11
|
-
|
|
12
|
-
from core.logging import get_logger
|
|
13
|
-
from services.pricing import get_pricing_service
|
|
14
|
-
|
|
15
|
-
logger = get_logger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
async def _track_search_usage(
|
|
19
|
-
node_id: str,
|
|
20
|
-
service: str,
|
|
21
|
-
action: str,
|
|
22
|
-
resource_count: int = 1,
|
|
23
|
-
workflow_id: str = None,
|
|
24
|
-
session_id: str = "default"
|
|
25
|
-
) -> Dict[str, float]:
|
|
26
|
-
"""Track search API usage for cost calculation."""
|
|
27
|
-
from core.container import container
|
|
28
|
-
|
|
29
|
-
pricing = get_pricing_service()
|
|
30
|
-
cost_data = pricing.calculate_api_cost(service, action, resource_count)
|
|
31
|
-
|
|
32
|
-
db = container.database()
|
|
33
|
-
await db.save_api_usage_metric({
|
|
34
|
-
'session_id': session_id,
|
|
35
|
-
'node_id': node_id,
|
|
36
|
-
'workflow_id': workflow_id,
|
|
37
|
-
'service': service,
|
|
38
|
-
'operation': cost_data.get('operation', action),
|
|
39
|
-
'endpoint': action,
|
|
40
|
-
'resource_count': resource_count,
|
|
41
|
-
'cost': cost_data.get('total_cost', 0.0)
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
logger.debug(f"[Search] Tracked {service} usage: {action} x{resource_count} = ${cost_data.get('total_cost', 0):.6f}")
|
|
45
|
-
return cost_data
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
async def _get_api_key(provider: str) -> str:
|
|
49
|
-
"""Get API key from encrypted credentials."""
|
|
50
|
-
from core.container import container
|
|
51
|
-
auth_service = container.auth_service()
|
|
52
|
-
api_key = await auth_service.get_api_key(provider)
|
|
53
|
-
if not api_key:
|
|
54
|
-
raise ValueError(
|
|
55
|
-
f"{provider} API key not configured. "
|
|
56
|
-
f"Please add it in Credentials > Search."
|
|
57
|
-
)
|
|
58
|
-
return api_key
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# =============================================================================
|
|
62
|
-
# BRAVE SEARCH HANDLER
|
|
63
|
-
# =============================================================================
|
|
64
|
-
|
|
65
|
-
async def handle_brave_search(
|
|
66
|
-
node_id: str,
|
|
67
|
-
node_type: str,
|
|
68
|
-
parameters: Dict[str, Any],
|
|
69
|
-
context: Dict[str, Any]
|
|
70
|
-
) -> Dict[str, Any]:
|
|
71
|
-
"""Handle Brave Search API requests.
|
|
72
|
-
|
|
73
|
-
API: GET https://api.search.brave.com/res/v1/web/search
|
|
74
|
-
Auth: X-Subscription-Token header
|
|
75
|
-
"""
|
|
76
|
-
start_time = time.time()
|
|
77
|
-
|
|
78
|
-
query = parameters.get('query', '').strip()
|
|
79
|
-
if not query:
|
|
80
|
-
return {"success": False, "error": "Search query is required"}
|
|
81
|
-
|
|
82
|
-
max_results = parameters.get('maxResults', 10)
|
|
83
|
-
country = parameters.get('country', '')
|
|
84
|
-
search_lang = parameters.get('searchLang', '')
|
|
85
|
-
safe_search = parameters.get('safeSearch', 'moderate')
|
|
86
|
-
|
|
87
|
-
try:
|
|
88
|
-
api_key = await _get_api_key('brave_search')
|
|
89
|
-
|
|
90
|
-
params = {
|
|
91
|
-
'q': query,
|
|
92
|
-
'count': min(max_results, 100),
|
|
93
|
-
}
|
|
94
|
-
if country:
|
|
95
|
-
params['country'] = country
|
|
96
|
-
if search_lang:
|
|
97
|
-
params['search_lang'] = search_lang
|
|
98
|
-
if safe_search:
|
|
99
|
-
params['safesearch'] = safe_search
|
|
100
|
-
|
|
101
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
102
|
-
response = await client.get(
|
|
103
|
-
'https://api.search.brave.com/res/v1/web/search',
|
|
104
|
-
headers={
|
|
105
|
-
'X-Subscription-Token': api_key,
|
|
106
|
-
'Accept': 'application/json',
|
|
107
|
-
},
|
|
108
|
-
params=params,
|
|
109
|
-
)
|
|
110
|
-
response.raise_for_status()
|
|
111
|
-
data = response.json()
|
|
112
|
-
|
|
113
|
-
# Extract web results
|
|
114
|
-
web_results = data.get('web', {}).get('results', [])
|
|
115
|
-
results = []
|
|
116
|
-
for item in web_results[:max_results]:
|
|
117
|
-
results.append({
|
|
118
|
-
'title': item.get('title', ''),
|
|
119
|
-
'snippet': item.get('description', ''),
|
|
120
|
-
'url': item.get('url', ''),
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
execution_time = time.time() - start_time
|
|
124
|
-
|
|
125
|
-
# Track usage
|
|
126
|
-
workflow_id = context.get('workflow_id')
|
|
127
|
-
session_id = context.get('session_id', 'default')
|
|
128
|
-
await _track_search_usage(node_id, 'brave_search', 'web_search', 1, workflow_id, session_id)
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
"success": True,
|
|
132
|
-
"result": {
|
|
133
|
-
"query": query,
|
|
134
|
-
"results": results,
|
|
135
|
-
"result_count": len(results),
|
|
136
|
-
"provider": "brave_search",
|
|
137
|
-
},
|
|
138
|
-
"execution_time": round(execution_time, 3),
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
except httpx.HTTPStatusError as e:
|
|
142
|
-
logger.error(f"[BraveSearch] API error: {e.response.status_code} - {e.response.text}")
|
|
143
|
-
return {"success": False, "error": f"Brave Search API error: {e.response.status_code}"}
|
|
144
|
-
except ValueError as e:
|
|
145
|
-
return {"success": False, "error": str(e)}
|
|
146
|
-
except Exception as e:
|
|
147
|
-
logger.error(f"[BraveSearch] Error: {e}")
|
|
148
|
-
return {"success": False, "error": f"Brave Search failed: {str(e)}"}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
# =============================================================================
|
|
152
|
-
# SERPER SEARCH HANDLER
|
|
153
|
-
# =============================================================================
|
|
154
|
-
|
|
155
|
-
async def handle_serper_search(
|
|
156
|
-
node_id: str,
|
|
157
|
-
node_type: str,
|
|
158
|
-
parameters: Dict[str, Any],
|
|
159
|
-
context: Dict[str, Any]
|
|
160
|
-
) -> Dict[str, Any]:
|
|
161
|
-
"""Handle Serper (Google) Search API requests.
|
|
162
|
-
|
|
163
|
-
API: POST https://google.serper.dev/search
|
|
164
|
-
Auth: X-API-KEY header
|
|
165
|
-
"""
|
|
166
|
-
start_time = time.time()
|
|
167
|
-
|
|
168
|
-
query = parameters.get('query', '').strip()
|
|
169
|
-
if not query:
|
|
170
|
-
return {"success": False, "error": "Search query is required"}
|
|
171
|
-
|
|
172
|
-
max_results = parameters.get('maxResults', 10)
|
|
173
|
-
search_type = parameters.get('searchType', 'search')
|
|
174
|
-
country = parameters.get('country', '')
|
|
175
|
-
language = parameters.get('language', '')
|
|
176
|
-
|
|
177
|
-
try:
|
|
178
|
-
api_key = await _get_api_key('serper')
|
|
179
|
-
|
|
180
|
-
body: Dict[str, Any] = {
|
|
181
|
-
'q': query,
|
|
182
|
-
'num': min(max_results, 100),
|
|
183
|
-
}
|
|
184
|
-
if country:
|
|
185
|
-
body['gl'] = country
|
|
186
|
-
if language:
|
|
187
|
-
body['hl'] = language
|
|
188
|
-
|
|
189
|
-
# Map search type to endpoint
|
|
190
|
-
endpoint_map = {
|
|
191
|
-
'search': 'https://google.serper.dev/search',
|
|
192
|
-
'news': 'https://google.serper.dev/news',
|
|
193
|
-
'images': 'https://google.serper.dev/images',
|
|
194
|
-
'places': 'https://google.serper.dev/places',
|
|
195
|
-
}
|
|
196
|
-
endpoint = endpoint_map.get(search_type, 'https://google.serper.dev/search')
|
|
197
|
-
|
|
198
|
-
async with httpx.AsyncClient(timeout=30.0) as client:
|
|
199
|
-
response = await client.post(
|
|
200
|
-
endpoint,
|
|
201
|
-
headers={
|
|
202
|
-
'X-API-KEY': api_key,
|
|
203
|
-
'Content-Type': 'application/json',
|
|
204
|
-
},
|
|
205
|
-
json=body,
|
|
206
|
-
)
|
|
207
|
-
response.raise_for_status()
|
|
208
|
-
data = response.json()
|
|
209
|
-
|
|
210
|
-
# Extract results based on search type
|
|
211
|
-
results = []
|
|
212
|
-
if search_type == 'search':
|
|
213
|
-
for item in data.get('organic', [])[:max_results]:
|
|
214
|
-
results.append({
|
|
215
|
-
'title': item.get('title', ''),
|
|
216
|
-
'snippet': item.get('snippet', ''),
|
|
217
|
-
'url': item.get('link', ''),
|
|
218
|
-
'position': item.get('position'),
|
|
219
|
-
})
|
|
220
|
-
elif search_type == 'news':
|
|
221
|
-
for item in data.get('news', [])[:max_results]:
|
|
222
|
-
results.append({
|
|
223
|
-
'title': item.get('title', ''),
|
|
224
|
-
'snippet': item.get('snippet', ''),
|
|
225
|
-
'url': item.get('link', ''),
|
|
226
|
-
'date': item.get('date', ''),
|
|
227
|
-
'source': item.get('source', ''),
|
|
228
|
-
})
|
|
229
|
-
elif search_type == 'images':
|
|
230
|
-
for item in data.get('images', [])[:max_results]:
|
|
231
|
-
results.append({
|
|
232
|
-
'title': item.get('title', ''),
|
|
233
|
-
'imageUrl': item.get('imageUrl', ''),
|
|
234
|
-
'url': item.get('link', ''),
|
|
235
|
-
})
|
|
236
|
-
elif search_type == 'places':
|
|
237
|
-
for item in data.get('places', [])[:max_results]:
|
|
238
|
-
results.append({
|
|
239
|
-
'title': item.get('title', ''),
|
|
240
|
-
'address': item.get('address', ''),
|
|
241
|
-
'rating': item.get('rating'),
|
|
242
|
-
'url': item.get('website', ''),
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
# Include knowledge graph if available
|
|
246
|
-
knowledge_graph = data.get('knowledgeGraph')
|
|
247
|
-
|
|
248
|
-
execution_time = time.time() - start_time
|
|
249
|
-
|
|
250
|
-
# Track usage
|
|
251
|
-
workflow_id = context.get('workflow_id')
|
|
252
|
-
session_id = context.get('session_id', 'default')
|
|
253
|
-
await _track_search_usage(node_id, 'serper', 'web_search', 1, workflow_id, session_id)
|
|
254
|
-
|
|
255
|
-
result = {
|
|
256
|
-
"query": query,
|
|
257
|
-
"results": results,
|
|
258
|
-
"result_count": len(results),
|
|
259
|
-
"search_type": search_type,
|
|
260
|
-
"provider": "serper",
|
|
261
|
-
}
|
|
262
|
-
if knowledge_graph:
|
|
263
|
-
result["knowledge_graph"] = knowledge_graph
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
"success": True,
|
|
267
|
-
"result": result,
|
|
268
|
-
"execution_time": round(execution_time, 3),
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
except httpx.HTTPStatusError as e:
|
|
272
|
-
logger.error(f"[Serper] API error: {e.response.status_code} - {e.response.text}")
|
|
273
|
-
return {"success": False, "error": f"Serper API error: {e.response.status_code}"}
|
|
274
|
-
except ValueError as e:
|
|
275
|
-
return {"success": False, "error": str(e)}
|
|
276
|
-
except Exception as e:
|
|
277
|
-
logger.error(f"[Serper] Error: {e}")
|
|
278
|
-
return {"success": False, "error": f"Serper search failed: {str(e)}"}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
# =============================================================================
|
|
282
|
-
# PERPLEXITY SEARCH HANDLER
|
|
283
|
-
# =============================================================================
|
|
284
|
-
|
|
285
|
-
async def handle_perplexity_search(
|
|
286
|
-
node_id: str,
|
|
287
|
-
node_type: str,
|
|
288
|
-
parameters: Dict[str, Any],
|
|
289
|
-
context: Dict[str, Any]
|
|
290
|
-
) -> Dict[str, Any]:
|
|
291
|
-
"""Handle Perplexity Sonar AI search requests.
|
|
292
|
-
|
|
293
|
-
API: POST https://api.perplexity.ai/chat/completions
|
|
294
|
-
Auth: Authorization: Bearer header
|
|
295
|
-
"""
|
|
296
|
-
start_time = time.time()
|
|
297
|
-
|
|
298
|
-
query = parameters.get('query', '').strip()
|
|
299
|
-
if not query:
|
|
300
|
-
return {"success": False, "error": "Search query is required"}
|
|
301
|
-
|
|
302
|
-
model = parameters.get('model', 'sonar')
|
|
303
|
-
search_recency_filter = parameters.get('searchRecencyFilter', '')
|
|
304
|
-
return_images = parameters.get('returnImages', False)
|
|
305
|
-
return_related_questions = parameters.get('returnRelatedQuestions', False)
|
|
306
|
-
|
|
307
|
-
try:
|
|
308
|
-
api_key = await _get_api_key('perplexity')
|
|
309
|
-
|
|
310
|
-
body: Dict[str, Any] = {
|
|
311
|
-
'model': model,
|
|
312
|
-
'messages': [
|
|
313
|
-
{'role': 'user', 'content': query}
|
|
314
|
-
],
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if search_recency_filter:
|
|
318
|
-
body['search_recency_filter'] = search_recency_filter
|
|
319
|
-
if return_images:
|
|
320
|
-
body['return_images'] = True
|
|
321
|
-
if return_related_questions:
|
|
322
|
-
body['return_related_questions'] = True
|
|
323
|
-
|
|
324
|
-
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
325
|
-
response = await client.post(
|
|
326
|
-
'https://api.perplexity.ai/chat/completions',
|
|
327
|
-
headers={
|
|
328
|
-
'Authorization': f'Bearer {api_key}',
|
|
329
|
-
'Content-Type': 'application/json',
|
|
330
|
-
},
|
|
331
|
-
json=body,
|
|
332
|
-
)
|
|
333
|
-
response.raise_for_status()
|
|
334
|
-
data = response.json()
|
|
335
|
-
|
|
336
|
-
# Extract answer and citations
|
|
337
|
-
choices = data.get('choices', [])
|
|
338
|
-
answer = ''
|
|
339
|
-
if choices:
|
|
340
|
-
message = choices[0].get('message', {})
|
|
341
|
-
answer = message.get('content', '')
|
|
342
|
-
|
|
343
|
-
citations = data.get('citations', [])
|
|
344
|
-
images = data.get('images', [])
|
|
345
|
-
related_questions = data.get('related_questions', [])
|
|
346
|
-
|
|
347
|
-
# Build results from citations
|
|
348
|
-
results = []
|
|
349
|
-
for url in citations:
|
|
350
|
-
results.append({'url': url})
|
|
351
|
-
|
|
352
|
-
execution_time = time.time() - start_time
|
|
353
|
-
|
|
354
|
-
# Track usage
|
|
355
|
-
workflow_id = context.get('workflow_id')
|
|
356
|
-
session_id = context.get('session_id', 'default')
|
|
357
|
-
await _track_search_usage(node_id, 'perplexity', 'sonar_search', 1, workflow_id, session_id)
|
|
358
|
-
|
|
359
|
-
result: Dict[str, Any] = {
|
|
360
|
-
"query": query,
|
|
361
|
-
"answer": answer,
|
|
362
|
-
"citations": citations,
|
|
363
|
-
"results": results,
|
|
364
|
-
"model": model,
|
|
365
|
-
"provider": "perplexity",
|
|
366
|
-
}
|
|
367
|
-
if images:
|
|
368
|
-
result["images"] = images
|
|
369
|
-
if related_questions:
|
|
370
|
-
result["related_questions"] = related_questions
|
|
371
|
-
|
|
372
|
-
return {
|
|
373
|
-
"success": True,
|
|
374
|
-
"result": result,
|
|
375
|
-
"execution_time": round(execution_time, 3),
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
except httpx.HTTPStatusError as e:
|
|
379
|
-
logger.error(f"[Perplexity] API error: {e.response.status_code} - {e.response.text}")
|
|
380
|
-
return {"success": False, "error": f"Perplexity API error: {e.response.status_code}"}
|
|
381
|
-
except ValueError as e:
|
|
382
|
-
return {"success": False, "error": str(e)}
|
|
383
|
-
except Exception as e:
|
|
384
|
-
logger.error(f"[Perplexity] Error: {e}")
|
|
385
|
-
return {"success": False, "error": f"Perplexity search failed: {str(e)}"}
|
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
"""Google Sheets node handlers using Google API Python client.
|
|
2
|
-
|
|
3
|
-
API Reference: https://developers.google.com/workspace/sheets/api/reference/rest
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import asyncio
|
|
7
|
-
import time
|
|
8
|
-
from typing import Any, Dict
|
|
9
|
-
|
|
10
|
-
from googleapiclient.discovery import build
|
|
11
|
-
|
|
12
|
-
from core.logging import get_logger
|
|
13
|
-
from services.handlers.google_auth import get_google_credentials
|
|
14
|
-
from services.pricing import get_pricing_service
|
|
15
|
-
|
|
16
|
-
logger = get_logger(__name__)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
async def _track_sheets_usage(
|
|
20
|
-
node_id: str,
|
|
21
|
-
action: str,
|
|
22
|
-
resource_count: int = 1,
|
|
23
|
-
workflow_id: str = None,
|
|
24
|
-
session_id: str = "default"
|
|
25
|
-
) -> Dict[str, float]:
|
|
26
|
-
"""Track Google Sheets API usage for analytics.
|
|
27
|
-
|
|
28
|
-
Note: Sheets API is free but rate limited (300 requests/min).
|
|
29
|
-
We track for analytics purposes with $0 cost.
|
|
30
|
-
"""
|
|
31
|
-
from core.container import container
|
|
32
|
-
|
|
33
|
-
pricing = get_pricing_service()
|
|
34
|
-
cost_data = pricing.calculate_api_cost('google_sheets', action, resource_count)
|
|
35
|
-
|
|
36
|
-
db = container.database()
|
|
37
|
-
await db.save_api_usage_metric({
|
|
38
|
-
'session_id': session_id,
|
|
39
|
-
'node_id': node_id,
|
|
40
|
-
'workflow_id': workflow_id,
|
|
41
|
-
'service': 'google_sheets',
|
|
42
|
-
'operation': cost_data.get('operation', action),
|
|
43
|
-
'endpoint': action,
|
|
44
|
-
'resource_count': resource_count,
|
|
45
|
-
'cost': cost_data.get('total_cost', 0.0)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
logger.debug(f"[Sheets] Tracked usage: {action} x{resource_count}")
|
|
49
|
-
return cost_data
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
async def _get_sheets_service(
|
|
53
|
-
parameters: Dict[str, Any],
|
|
54
|
-
context: Dict[str, Any]
|
|
55
|
-
):
|
|
56
|
-
"""Get authenticated Google Sheets service."""
|
|
57
|
-
creds = await get_google_credentials(parameters, context)
|
|
58
|
-
|
|
59
|
-
def build_service():
|
|
60
|
-
return build("sheets", "v4", credentials=creds)
|
|
61
|
-
|
|
62
|
-
loop = asyncio.get_event_loop()
|
|
63
|
-
return await loop.run_in_executor(None, build_service)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
async def handle_sheets_read(
|
|
67
|
-
node_id: str,
|
|
68
|
-
node_type: str,
|
|
69
|
-
parameters: Dict[str, Any],
|
|
70
|
-
context: Dict[str, Any]
|
|
71
|
-
) -> Dict[str, Any]:
|
|
72
|
-
"""Read data from a Google Sheets spreadsheet.
|
|
73
|
-
|
|
74
|
-
Parameters:
|
|
75
|
-
spreadsheet_id: ID of the spreadsheet (required)
|
|
76
|
-
range: A1 notation range (e.g., "Sheet1!A1:D10") (required)
|
|
77
|
-
value_render_option: How values should be rendered ('FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA')
|
|
78
|
-
major_dimension: Rows or columns first ('ROWS', 'COLUMNS')
|
|
79
|
-
"""
|
|
80
|
-
start_time = time.time()
|
|
81
|
-
|
|
82
|
-
try:
|
|
83
|
-
service = await _get_sheets_service(parameters, context)
|
|
84
|
-
|
|
85
|
-
spreadsheet_id = parameters.get('spreadsheet_id', '')
|
|
86
|
-
range_notation = parameters.get('range', '')
|
|
87
|
-
value_render = parameters.get('value_render_option', 'FORMATTED_VALUE')
|
|
88
|
-
major_dimension = parameters.get('major_dimension', 'ROWS')
|
|
89
|
-
|
|
90
|
-
if not spreadsheet_id:
|
|
91
|
-
raise ValueError("Spreadsheet ID is required")
|
|
92
|
-
if not range_notation:
|
|
93
|
-
raise ValueError("Range is required (e.g., 'Sheet1!A1:D10')")
|
|
94
|
-
|
|
95
|
-
workflow_id = context.get('workflow_id')
|
|
96
|
-
session_id = context.get('session_id', 'default')
|
|
97
|
-
|
|
98
|
-
def read_values():
|
|
99
|
-
return service.spreadsheets().values().get(
|
|
100
|
-
spreadsheetId=spreadsheet_id,
|
|
101
|
-
range=range_notation,
|
|
102
|
-
valueRenderOption=value_render,
|
|
103
|
-
majorDimension=major_dimension
|
|
104
|
-
).execute()
|
|
105
|
-
|
|
106
|
-
loop = asyncio.get_event_loop()
|
|
107
|
-
result = await loop.run_in_executor(None, read_values)
|
|
108
|
-
|
|
109
|
-
values = result.get('values', [])
|
|
110
|
-
|
|
111
|
-
await _track_sheets_usage(node_id, 'read', len(values), workflow_id, session_id)
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
"success": True,
|
|
115
|
-
"result": {
|
|
116
|
-
"values": values,
|
|
117
|
-
"range": result.get('range'),
|
|
118
|
-
"rows": len(values),
|
|
119
|
-
"columns": len(values[0]) if values else 0,
|
|
120
|
-
"major_dimension": result.get('majorDimension'),
|
|
121
|
-
},
|
|
122
|
-
"execution_time": time.time() - start_time
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
except Exception as e:
|
|
126
|
-
logger.error(f"Sheets read error: {e}")
|
|
127
|
-
return {"success": False, "error": str(e), "execution_time": time.time() - start_time}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
async def handle_sheets_write(
|
|
131
|
-
node_id: str,
|
|
132
|
-
node_type: str,
|
|
133
|
-
parameters: Dict[str, Any],
|
|
134
|
-
context: Dict[str, Any]
|
|
135
|
-
) -> Dict[str, Any]:
|
|
136
|
-
"""Write data to a Google Sheets spreadsheet.
|
|
137
|
-
|
|
138
|
-
Parameters:
|
|
139
|
-
spreadsheet_id: ID of the spreadsheet (required)
|
|
140
|
-
range: A1 notation range (e.g., "Sheet1!A1") (required)
|
|
141
|
-
values: 2D array of values to write (required)
|
|
142
|
-
value_input_option: How input values should be interpreted ('RAW', 'USER_ENTERED')
|
|
143
|
-
"""
|
|
144
|
-
start_time = time.time()
|
|
145
|
-
|
|
146
|
-
try:
|
|
147
|
-
service = await _get_sheets_service(parameters, context)
|
|
148
|
-
|
|
149
|
-
spreadsheet_id = parameters.get('spreadsheet_id', '')
|
|
150
|
-
range_notation = parameters.get('range', '')
|
|
151
|
-
values = parameters.get('values', [])
|
|
152
|
-
value_input = parameters.get('value_input_option', 'USER_ENTERED')
|
|
153
|
-
|
|
154
|
-
if not spreadsheet_id:
|
|
155
|
-
raise ValueError("Spreadsheet ID is required")
|
|
156
|
-
if not range_notation:
|
|
157
|
-
raise ValueError("Range is required (e.g., 'Sheet1!A1')")
|
|
158
|
-
if not values:
|
|
159
|
-
raise ValueError("Values are required")
|
|
160
|
-
|
|
161
|
-
# Parse values if string (JSON format)
|
|
162
|
-
if isinstance(values, str):
|
|
163
|
-
import json
|
|
164
|
-
values = json.loads(values)
|
|
165
|
-
|
|
166
|
-
# Ensure values is a 2D array
|
|
167
|
-
if values and not isinstance(values[0], list):
|
|
168
|
-
values = [values]
|
|
169
|
-
|
|
170
|
-
workflow_id = context.get('workflow_id')
|
|
171
|
-
session_id = context.get('session_id', 'default')
|
|
172
|
-
|
|
173
|
-
body = {'values': values}
|
|
174
|
-
|
|
175
|
-
def write_values():
|
|
176
|
-
return service.spreadsheets().values().update(
|
|
177
|
-
spreadsheetId=spreadsheet_id,
|
|
178
|
-
range=range_notation,
|
|
179
|
-
valueInputOption=value_input,
|
|
180
|
-
body=body
|
|
181
|
-
).execute()
|
|
182
|
-
|
|
183
|
-
loop = asyncio.get_event_loop()
|
|
184
|
-
result = await loop.run_in_executor(None, write_values)
|
|
185
|
-
|
|
186
|
-
await _track_sheets_usage(node_id, 'write', result.get('updatedCells', 0), workflow_id, session_id)
|
|
187
|
-
|
|
188
|
-
return {
|
|
189
|
-
"success": True,
|
|
190
|
-
"result": {
|
|
191
|
-
"updated_range": result.get('updatedRange'),
|
|
192
|
-
"updated_rows": result.get('updatedRows'),
|
|
193
|
-
"updated_columns": result.get('updatedColumns'),
|
|
194
|
-
"updated_cells": result.get('updatedCells'),
|
|
195
|
-
},
|
|
196
|
-
"execution_time": time.time() - start_time
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
except Exception as e:
|
|
200
|
-
logger.error(f"Sheets write error: {e}")
|
|
201
|
-
return {"success": False, "error": str(e), "execution_time": time.time() - start_time}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
async def handle_sheets_append(
|
|
205
|
-
node_id: str,
|
|
206
|
-
node_type: str,
|
|
207
|
-
parameters: Dict[str, Any],
|
|
208
|
-
context: Dict[str, Any]
|
|
209
|
-
) -> Dict[str, Any]:
|
|
210
|
-
"""Append rows to a Google Sheets spreadsheet.
|
|
211
|
-
|
|
212
|
-
Parameters:
|
|
213
|
-
spreadsheet_id: ID of the spreadsheet (required)
|
|
214
|
-
range: A1 notation range (e.g., "Sheet1!A:D") (required)
|
|
215
|
-
values: 2D array of values to append (required)
|
|
216
|
-
value_input_option: How input values should be interpreted ('RAW', 'USER_ENTERED')
|
|
217
|
-
insert_data_option: How the input data should be inserted ('OVERWRITE', 'INSERT_ROWS')
|
|
218
|
-
"""
|
|
219
|
-
start_time = time.time()
|
|
220
|
-
|
|
221
|
-
try:
|
|
222
|
-
service = await _get_sheets_service(parameters, context)
|
|
223
|
-
|
|
224
|
-
spreadsheet_id = parameters.get('spreadsheet_id', '')
|
|
225
|
-
range_notation = parameters.get('range', '')
|
|
226
|
-
values = parameters.get('values', [])
|
|
227
|
-
value_input = parameters.get('value_input_option', 'USER_ENTERED')
|
|
228
|
-
insert_option = parameters.get('insert_data_option', 'INSERT_ROWS')
|
|
229
|
-
|
|
230
|
-
if not spreadsheet_id:
|
|
231
|
-
raise ValueError("Spreadsheet ID is required")
|
|
232
|
-
if not range_notation:
|
|
233
|
-
raise ValueError("Range is required (e.g., 'Sheet1!A:D')")
|
|
234
|
-
if not values:
|
|
235
|
-
raise ValueError("Values are required")
|
|
236
|
-
|
|
237
|
-
# Parse values if string (JSON format)
|
|
238
|
-
if isinstance(values, str):
|
|
239
|
-
import json
|
|
240
|
-
values = json.loads(values)
|
|
241
|
-
|
|
242
|
-
# Ensure values is a 2D array
|
|
243
|
-
if values and not isinstance(values[0], list):
|
|
244
|
-
values = [values]
|
|
245
|
-
|
|
246
|
-
workflow_id = context.get('workflow_id')
|
|
247
|
-
session_id = context.get('session_id', 'default')
|
|
248
|
-
|
|
249
|
-
body = {'values': values}
|
|
250
|
-
|
|
251
|
-
def append_values():
|
|
252
|
-
return service.spreadsheets().values().append(
|
|
253
|
-
spreadsheetId=spreadsheet_id,
|
|
254
|
-
range=range_notation,
|
|
255
|
-
valueInputOption=value_input,
|
|
256
|
-
insertDataOption=insert_option,
|
|
257
|
-
body=body
|
|
258
|
-
).execute()
|
|
259
|
-
|
|
260
|
-
loop = asyncio.get_event_loop()
|
|
261
|
-
result = await loop.run_in_executor(None, append_values)
|
|
262
|
-
|
|
263
|
-
updates = result.get('updates', {})
|
|
264
|
-
|
|
265
|
-
await _track_sheets_usage(node_id, 'append', updates.get('updatedCells', 0), workflow_id, session_id)
|
|
266
|
-
|
|
267
|
-
return {
|
|
268
|
-
"success": True,
|
|
269
|
-
"result": {
|
|
270
|
-
"updated_range": updates.get('updatedRange'),
|
|
271
|
-
"updated_rows": updates.get('updatedRows'),
|
|
272
|
-
"updated_columns": updates.get('updatedColumns'),
|
|
273
|
-
"updated_cells": updates.get('updatedCells'),
|
|
274
|
-
"table_range": result.get('tableRange'),
|
|
275
|
-
},
|
|
276
|
-
"execution_time": time.time() - start_time
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
except Exception as e:
|
|
280
|
-
logger.error(f"Sheets append error: {e}")
|
|
281
|
-
return {"success": False, "error": str(e), "execution_time": time.time() - start_time}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
# ============================================================================
|
|
285
|
-
# CONSOLIDATED DISPATCHER
|
|
286
|
-
# ============================================================================
|
|
287
|
-
|
|
288
|
-
async def handle_google_sheets(
|
|
289
|
-
node_id: str,
|
|
290
|
-
node_type: str,
|
|
291
|
-
parameters: Dict[str, Any],
|
|
292
|
-
context: Dict[str, Any]
|
|
293
|
-
) -> Dict[str, Any]:
|
|
294
|
-
"""Consolidated Sheets handler with operation dispatcher.
|
|
295
|
-
|
|
296
|
-
Routes to appropriate handler based on 'operation' parameter:
|
|
297
|
-
- read: Read data from spreadsheet
|
|
298
|
-
- write: Write data to spreadsheet
|
|
299
|
-
- append: Append rows to spreadsheet
|
|
300
|
-
"""
|
|
301
|
-
operation = parameters.get('operation', 'read')
|
|
302
|
-
|
|
303
|
-
if operation == 'read':
|
|
304
|
-
return await handle_sheets_read(node_id, node_type, parameters, context)
|
|
305
|
-
elif operation == 'write':
|
|
306
|
-
return await handle_sheets_write(node_id, node_type, parameters, context)
|
|
307
|
-
elif operation == 'append':
|
|
308
|
-
return await handle_sheets_append(node_id, node_type, parameters, context)
|
|
309
|
-
else:
|
|
310
|
-
return {
|
|
311
|
-
"success": False,
|
|
312
|
-
"error": f"Unknown Sheets operation: {operation}. Supported: read, write, append",
|
|
313
|
-
"execution_time": 0
|
|
314
|
-
}
|