machinaos 0.0.76 → 0.0.78
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/README.md +143 -107
- package/client/dist/assets/ActionBar-Du2MSFSz.js +1 -0
- package/client/dist/assets/ApiKeyInput-k2LBmBjb.js +1 -0
- package/client/dist/assets/ApiKeyPanel-C_bV9U0X.js +1 -0
- package/client/dist/assets/ApiUsageSection-CmVfwZzL.js +1 -0
- package/client/dist/assets/EmailPanel-CeKIMGu-.js +1 -0
- package/client/dist/assets/OAuthPanel-KA3t3Q2K.js +1 -0
- package/client/dist/assets/QrPairingPanel-NgNpJNuk.js +1 -0
- package/client/dist/assets/RateLimitSection-Du5YNVIA.js +1 -0
- package/client/dist/assets/StatusCard-DNLyayXc.js +1 -0
- package/client/dist/assets/index-DQ0nwhec.js +257 -0
- package/client/dist/assets/index-DxmbVskS.css +1 -0
- package/client/dist/assets/vendor-flow-CZmBvHRo.js +1 -0
- package/client/dist/assets/vendor-icons-CVrPjN2Q.js +22 -0
- package/client/dist/assets/vendor-markdown-CRou3yQ5.js +62 -0
- package/client/dist/assets/vendor-misc-C4VxKHs5.js +1 -0
- package/client/dist/assets/vendor-query-SzWcOU0G.js +1 -0
- package/client/dist/assets/vendor-radix-Dnos29jG.js +56 -0
- package/client/dist/assets/vendor-react-DvWIbVx0.js +1 -0
- package/client/dist/index.html +37 -3
- package/client/index.html +28 -1
- package/client/package.json +44 -40
- package/client/src/App.tsx +2 -0
- package/client/src/Dashboard.tsx +157 -45
- package/client/src/ParameterPanel.tsx +3 -5
- package/client/src/adapters/nodeSpecToDescription.ts +1 -0
- package/client/src/assets/icons/NodeIcon.tsx +32 -0
- package/client/src/assets/icons/index.ts +4 -0
- package/client/src/assets/icons/stripe.svg +1 -0
- package/client/src/assets/icons/themedGlyphs.ts +404 -0
- package/client/src/components/AIAgentNode.tsx +77 -53
- package/client/src/components/GenericNode.tsx +34 -52
- package/client/src/components/OutputPanel.tsx +64 -147
- package/client/src/components/ParameterRenderer.tsx +5 -3
- package/client/src/components/SkillEditorModal.tsx +9 -18
- package/client/src/components/SquareNode.tsx +97 -115
- package/client/src/components/StartNode.tsx +32 -42
- package/client/src/components/SvgFilterDefs.tsx +54 -0
- package/client/src/components/TeamMonitorNode.tsx +12 -14
- package/client/src/components/ToolkitNode.tsx +35 -60
- package/client/src/components/TriggerNode.tsx +43 -77
- package/client/src/components/__tests__/CredentialsModal.test.tsx +49 -45
- package/client/src/components/credentials/CredentialsModal.tsx +98 -30
- package/client/src/components/credentials/CredentialsPalette.tsx +73 -5
- package/client/src/components/credentials/catalogueAdapter.ts +17 -1
- package/client/src/components/credentials/panels/ApiKeyPanel.tsx +102 -37
- package/client/src/components/credentials/panels/EmailPanel.tsx +7 -19
- package/client/src/components/credentials/panels/OAuthPanel.tsx +5 -1
- package/client/src/components/credentials/panels/QrPairingPanel.tsx +1 -3
- package/client/src/components/credentials/primitives/ActionBar.tsx +7 -11
- package/client/src/components/credentials/primitives/OAuthConnect.tsx +19 -28
- package/client/src/components/credentials/sections/ProviderDefaultsSection.tsx +24 -3
- package/client/src/components/credentials/types.ts +12 -2
- package/client/src/components/credentials/useCredentialPanel.ts +43 -19
- package/client/src/components/icons/AIProviderIcons.tsx +16 -0
- package/client/src/components/onboarding/OnboardingWizard.tsx +23 -63
- package/client/src/components/onboarding/nodeRoleClasses.ts +23 -0
- package/client/src/components/onboarding/steps/CanvasStep.tsx +15 -21
- package/client/src/components/onboarding/steps/ConceptsStep.tsx +2 -11
- package/client/src/components/onboarding/steps/GetStartedStep.tsx +2 -10
- package/client/src/components/parameterPanel/InputSection.tsx +9 -7
- package/client/src/components/parameterPanel/MasterSkillEditor.tsx +84 -198
- package/client/src/components/parameterPanel/MiddleSection.tsx +57 -80
- package/client/src/components/parameterPanel/ToolSchemaEditor.tsx +31 -25
- package/client/src/components/parameterPanel/__tests__/InputSection.test.tsx +7 -2
- package/client/src/components/ui/AIResultModal.tsx +1 -1
- package/client/src/components/ui/CollapsibleSection.tsx +9 -5
- package/client/src/components/ui/CommandPalette.tsx +147 -0
- package/client/src/components/ui/CommandPaletteHost.tsx +189 -0
- package/client/src/components/ui/ComponentItem.tsx +13 -7
- package/client/src/components/ui/ComponentPalette.tsx +24 -13
- package/client/src/components/ui/ConsolePanel.tsx +19 -11
- package/client/src/components/ui/DropCap.tsx +28 -0
- package/client/src/components/ui/EditableNodeLabel.tsx +10 -2
- package/client/src/components/ui/InputNodesPanel.tsx +1 -1
- package/client/src/components/ui/Modal.tsx +38 -6
- package/client/src/components/ui/OutputDisplayPanel.tsx +1 -1
- package/client/src/components/ui/SettingsPanel.tsx +42 -13
- package/client/src/components/ui/StatusBar.tsx +108 -0
- package/client/src/components/ui/ThemeSwitcher.tsx +109 -0
- package/client/src/components/ui/TopToolbar.tsx +42 -25
- package/client/src/components/ui/WorkflowSidebar.tsx +32 -16
- package/client/src/components/ui/action-button.tsx +40 -15
- package/client/src/components/ui/button.tsx +24 -1
- package/client/src/components/ui/dropdown-menu.tsx +24 -2
- package/client/src/components/ui/input.tsx +19 -2
- package/client/src/components/ui/select.tsx +15 -0
- package/client/src/components/ui/textarea.tsx +15 -2
- package/client/src/contexts/AuthContext.tsx +148 -109
- package/client/src/contexts/ThemeContext.tsx +93 -17
- package/client/src/contexts/WebSocketContext.tsx +373 -206
- package/client/src/contexts/__tests__/AuthContext.test.tsx +221 -0
- package/client/src/hooks/__tests__/useDragVariable.test.ts +7 -1
- package/client/src/hooks/__tests__/useWorkflowOpsListener.test.ts +142 -0
- package/client/src/hooks/useAppTheme.ts +209 -7
- package/client/src/hooks/useAutoSkillEdges.ts +7 -2
- package/client/src/hooks/useCatalogueQuery.ts +67 -1
- package/client/src/hooks/useDragVariable.ts +1 -1
- package/client/src/hooks/useNodeAllowlist.ts +115 -8
- package/client/src/hooks/useOnboarding.ts +20 -8
- package/client/src/hooks/useParameterPanel.ts +2 -1
- package/client/src/hooks/useReactFlowNodes.ts +2 -1
- package/client/src/hooks/useSound.ts +185 -0
- package/client/src/hooks/useWorkflowManagement.ts +6 -8
- package/client/src/hooks/useWorkflowOpsListener.ts +90 -0
- package/client/src/index.css +65 -3
- package/client/src/lib/__tests__/connectionConfig.test.ts +91 -0
- package/client/src/lib/aiModelProviders.ts +8 -0
- package/client/src/lib/connectionConfig.ts +107 -0
- package/client/src/lib/queryPersist.ts +13 -5
- package/client/src/lib/sound.ts +393 -0
- package/client/src/main.tsx +20 -0
- package/client/src/store/useAppStore.ts +26 -0
- package/client/src/styles/canvasAnimations.ts +37 -36
- package/client/src/styles/theme.ts +36 -20
- package/client/src/test/setup.ts +1 -0
- package/client/src/themes/atomic.css +253 -0
- package/client/src/themes/base.css +373 -0
- package/client/src/themes/cyber.css +890 -0
- package/client/src/themes/dark.css +70 -0
- package/client/src/themes/edo.css +246 -0
- package/client/src/themes/greek.css +293 -0
- package/client/src/themes/light.css +78 -0
- package/client/src/themes/plague.css +253 -0
- package/client/src/themes/renaissance.css +727 -0
- package/client/src/themes/rot.css +249 -0
- package/client/src/themes/steampunk.css +272 -0
- package/client/src/themes/surveillance.css +289 -0
- package/client/src/themes/wasteland.css +250 -0
- package/client/src/types/INodeProperties.ts +5 -0
- package/client/src/types/NodeTypes.ts +11 -1
- package/client/src/types/__tests__/cloudEvents.test.ts +99 -0
- package/client/src/types/cloudEvents.ts +78 -0
- package/client/src/vite-env.d.ts +7 -0
- package/client/tsconfig.json +1 -1
- package/client/vite.config.js +62 -2
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/machina/commands/build.py +51 -7
- package/machina/pyproject.toml +4 -0
- package/machina/supervisor.py +12 -2
- package/machina/tree.py +71 -21
- package/package.json +4 -4
- package/scripts/install.js +16 -1
- package/server/config/ai_cli_providers.json +54 -0
- package/server/config/credential_providers.json +109 -2
- package/server/config/llm_defaults.json +24 -0
- package/server/config/model_registry.json +338 -499
- package/server/config/node_allowlist.json +16 -1
- package/server/config/pricing.json +8 -0
- package/server/constants.py +38 -15
- package/server/core/container.py +2 -2
- package/server/core/credentials_database.py +35 -2
- package/server/core/logging.py +4 -3
- package/server/main.py +99 -13
- package/server/models/node_metadata.py +1 -0
- package/server/nodejs/package.json +8 -6
- package/server/nodejs/src/index.ts +22 -5
- package/server/nodes/README.md +31 -4
- package/server/nodes/agent/_inline.py +2 -0
- package/server/nodes/agent/_specialized.py +6 -3
- package/server/nodes/agent/ai_agent.py +13 -3
- package/server/nodes/agent/chat_agent.py +6 -3
- package/server/nodes/agent/claude_code_agent.py +287 -75
- package/server/nodes/agent/codex_agent.py +239 -0
- package/server/nodes/agent/deep_agent.py +3 -3
- package/server/nodes/agent/rlm_agent.py +3 -3
- package/server/nodes/android/__init__.py +31 -1
- package/server/nodes/android/_base.py +9 -5
- package/server/{services/android_service.py → nodes/android/_dispatcher.py} +2 -2
- package/server/nodes/android/_handlers.py +154 -0
- package/server/nodes/android/_option_loaders.py +44 -0
- package/server/nodes/android/_refresh.py +127 -0
- package/server/{services/android → nodes/android/_relay}/client.py +4 -4
- package/server/{routers/android.py → nodes/android/_router.py} +27 -8
- package/server/nodes/browser/browser.py +2 -2
- package/server/nodes/code/_base.py +6 -2
- package/server/nodes/code/_claude_code.py +134 -0
- package/server/nodes/document/embedding_generator.py +3 -3
- package/server/nodes/document/http_scraper.py +3 -3
- package/server/nodes/document/vector_store.py +5 -5
- package/server/nodes/email/__init__.py +11 -1
- package/server/nodes/email/_filters.py +21 -0
- package/server/{services/himalaya_service.py → nodes/email/_himalaya.py} +6 -10
- package/server/{services/email_service.py → nodes/email/_service.py} +9 -13
- package/server/nodes/email/email_read.py +1 -1
- package/server/nodes/email/email_receive.py +54 -5
- package/server/nodes/email/email_send.py +1 -1
- package/server/nodes/filesystem/shell.py +24 -1
- package/server/nodes/google/__init__.py +55 -1
- package/server/{services/handlers/google_auth.py → nodes/google/_auth_helper.py} +8 -5
- package/server/nodes/google/_base.py +2 -2
- package/server/nodes/google/_credentials.py +5 -5
- package/server/nodes/google/_filters.py +25 -0
- package/server/nodes/google/_handlers.py +57 -0
- package/server/{services/google_oauth.py → nodes/google/_oauth.py} +195 -162
- package/server/nodes/google/_option_loaders.py +107 -0
- package/server/nodes/google/_refresh.py +66 -0
- package/server/nodes/google/_router.py +131 -0
- package/server/nodes/google/gmail_receive.py +41 -4
- package/server/nodes/groups.py +1 -0
- package/server/nodes/location/_credentials.py +45 -1
- package/server/{services/maps.py → nodes/location/_service.py} +18 -3
- package/server/nodes/location/gmaps_create.py +4 -4
- package/server/nodes/location/gmaps_locations.py +4 -4
- package/server/nodes/location/gmaps_nearby_places.py +4 -4
- package/server/nodes/model/_base.py +8 -3
- package/server/nodes/model/_credentials.py +96 -8
- package/server/nodes/model/_local_validator.py +345 -0
- package/server/nodes/model/lmstudio_chat_model.py +23 -0
- package/server/nodes/model/ollama_chat_model.py +25 -0
- package/server/nodes/proxy/_usage.py +2 -2
- package/server/nodes/proxy/proxy_config.py +14 -14
- package/server/nodes/proxy/proxy_request.py +4 -4
- package/server/nodes/scraper/_credentials.py +29 -1
- package/server/nodes/scraper/apify_actor.py +9 -9
- package/server/nodes/scraper/crawlee_scraper.py +5 -5
- package/server/nodes/search/brave_search.py +4 -0
- package/server/nodes/search/perplexity_search.py +9 -0
- package/server/nodes/search/serper_search.py +3 -0
- package/server/nodes/skill/simple_memory.py +12 -0
- package/server/nodes/social/_base.py +2 -2
- package/server/nodes/stripe/__init__.py +46 -0
- package/server/nodes/stripe/_credentials.py +33 -0
- package/server/nodes/stripe/_handlers.py +270 -0
- package/server/nodes/stripe/_install.py +127 -0
- package/server/nodes/stripe/_source.py +174 -0
- package/server/nodes/stripe/stripe_action.py +81 -0
- package/server/nodes/stripe/stripe_receive.py +92 -0
- package/server/nodes/telegram/_credentials.py +52 -1
- package/server/nodes/telegram/_handlers.py +19 -18
- package/server/nodes/telegram/_service.py +134 -32
- package/server/nodes/telegram/telegram_send.py +5 -6
- package/server/nodes/text/file_handler.py +2 -2
- package/server/nodes/text/text_generator.py +2 -2
- package/server/nodes/tool/agent_builder.py +630 -0
- package/server/nodes/tool/task_manager.py +144 -2
- package/server/nodes/twitter/__init__.py +38 -1
- package/server/nodes/twitter/_base.py +7 -7
- package/server/nodes/twitter/_credentials.py +1 -1
- package/server/nodes/twitter/_filters.py +37 -0
- package/server/nodes/twitter/_handlers.py +77 -0
- package/server/nodes/twitter/_oauth.py +124 -0
- package/server/nodes/twitter/_refresh.py +78 -0
- package/server/nodes/twitter/_router.py +29 -0
- package/server/nodes/twitter/twitter_receive.py +4 -0
- package/server/nodes/visuals.json +64 -19
- package/server/nodes/whatsapp/__init__.py +45 -5
- package/server/nodes/whatsapp/_base.py +3 -3
- package/server/nodes/whatsapp/_filters.py +137 -0
- package/server/nodes/whatsapp/_handlers.py +167 -0
- package/server/nodes/whatsapp/_option_loaders.py +68 -0
- package/server/nodes/whatsapp/_refresh.py +62 -0
- package/server/nodes/whatsapp/_runtime.py +1 -1
- package/server/pyproject.toml +29 -7
- package/server/routers/schemas.py +2 -2
- package/server/routers/webhook.py +26 -9
- package/server/routers/websocket.py +149 -810
- package/server/services/ai.py +89 -8
- package/server/services/auth.py +220 -43
- package/server/services/claude_oauth.py +126 -100
- package/server/services/cli_agent/__init__.py +78 -0
- package/server/services/cli_agent/_handlers.py +237 -0
- package/server/services/cli_agent/config.py +112 -0
- package/server/services/cli_agent/factory.py +48 -0
- package/server/services/cli_agent/lockfile.py +141 -0
- package/server/services/cli_agent/mcp_server.py +482 -0
- package/server/services/cli_agent/protocol.py +173 -0
- package/server/services/cli_agent/providers/__init__.py +9 -0
- package/server/services/cli_agent/providers/anthropic_claude.py +419 -0
- package/server/services/cli_agent/providers/google_gemini.py +80 -0
- package/server/services/cli_agent/providers/openai_codex.py +310 -0
- package/server/services/cli_agent/service.py +607 -0
- package/server/services/cli_agent/session.py +618 -0
- package/server/services/cli_agent/types.py +227 -0
- package/server/services/cli_agent/workflow_tools.py +233 -0
- package/server/services/credential_registry.py +26 -1
- package/server/services/deployment/manager.py +26 -145
- package/server/services/deployment/poll_registry.py +59 -0
- package/server/services/event_waiter.py +76 -246
- package/server/services/events/__init__.py +54 -0
- package/server/services/events/cli.py +78 -0
- package/server/services/events/daemon.py +163 -0
- package/server/services/events/envelope.py +281 -0
- package/server/services/events/lifecycle.py +99 -0
- package/server/services/events/oauth_lifecycle.py +534 -0
- package/server/services/events/polling.py +60 -0
- package/server/services/events/push.py +36 -0
- package/server/services/events/source.py +63 -0
- package/server/services/events/triggers.py +118 -0
- package/server/services/events/verifiers/__init__.py +25 -0
- package/server/services/events/verifiers/base.py +28 -0
- package/server/services/events/verifiers/github.py +25 -0
- package/server/services/events/verifiers/hmac_basic.py +32 -0
- package/server/services/events/verifiers/standard_webhooks.py +47 -0
- package/server/services/events/verifiers/stripe.py +42 -0
- package/server/services/events/webhook.py +105 -0
- package/server/services/handlers/tools.py +28 -186
- package/server/services/llm/config.py +7 -0
- package/server/services/llm/factory.py +8 -2
- package/server/services/memory/__init__.py +52 -0
- package/server/services/memory/jsonl.py +80 -0
- package/server/services/memory/markdown.py +65 -0
- package/server/services/memory/state.py +112 -0
- package/server/services/memory/vector_store.py +40 -0
- package/server/services/model_registry.py +76 -0
- package/server/services/node_allowlist.py +71 -15
- package/server/services/node_executor.py +2 -2
- package/server/services/node_output_schemas.py +21 -10
- package/server/services/node_spec.py +1 -1
- package/server/services/oauth_utils.py +1 -1
- package/server/services/plugin/__init__.py +2 -0
- package/server/services/plugin/base.py +44 -2
- package/server/services/plugin/credential.py +288 -1
- package/server/services/plugin/deps.py +105 -0
- package/server/services/plugin/edge_walker.py +12 -4
- package/server/services/plugin/oauth.py +381 -0
- package/server/services/plugin/polling.py +247 -0
- package/server/services/plugin/registry.py +145 -0
- package/server/services/plugin/singleton.py +65 -0
- package/server/services/plugin/ws.py +81 -0
- package/server/services/process_service.py +31 -2
- package/server/services/status_broadcaster.py +155 -238
- package/server/services/temporal/workflow.py +7 -7
- package/server/services/workflow.py +21 -3
- package/server/services/ws_handler_registry.py +111 -28
- package/server/skills/GUIDE.md +16 -1
- package/server/skills/assistant/agent-builder-skill/SKILL.md +166 -0
- package/server/skills/payments_agent/stripe-skill/SKILL.md +306 -0
- package/server/tests/credentials/test_auth_service.py +16 -9
- package/server/tests/credentials/test_credential_broadcasts.py +219 -0
- package/server/tests/credentials/test_google_oauth.py +6 -6
- package/server/tests/credentials/test_oauth_utils.py +1 -1
- package/server/tests/credentials/test_twitter_oauth.py +2 -2
- package/server/tests/credentials/test_websocket_handlers.py +44 -20
- package/server/tests/llm/test_factory.py +1 -0
- package/server/tests/llm/test_wiring.py +5 -1
- package/server/tests/nodes/_compat.py +24 -24
- package/server/tests/nodes/test_agent_builder.py +439 -0
- package/server/tests/nodes/test_ai_tools.py +18 -14
- package/server/tests/nodes/test_code_fs_process.py +17 -8
- package/server/tests/nodes/test_email.py +10 -9
- package/server/tests/nodes/test_google_workspace.py +2 -2
- package/server/tests/nodes/test_specialized_agents.py +100 -53
- package/server/tests/nodes/test_stripe_plugin.py +293 -0
- package/server/tests/nodes/test_telegram_social.py +4 -4
- package/server/tests/nodes/test_twitter.py +1 -1
- package/server/tests/nodes/test_web_automation.py +2 -2
- package/server/tests/nodes/test_whatsapp.py +9 -9
- package/server/tests/services/cli_agent/__init__.py +0 -0
- package/server/tests/services/cli_agent/test_mcp_server.py +432 -0
- package/server/tests/services/cli_agent/test_providers.py +358 -0
- package/server/tests/services/cli_agent/test_service.py +298 -0
- package/server/tests/services/memory/__init__.py +0 -0
- package/server/tests/services/memory/test_jsonl.py +188 -0
- package/server/tests/services/test_events.py +333 -0
- package/server/tests/test_node_spec.py +56 -16
- package/server/tests/test_plugin_helpers.py +116 -0
- package/server/tests/test_plugin_self_containment.py +486 -0
- package/server/tests/test_status_broadcasts.py +425 -0
- package/workflows/{AI Assistant_workflow-1777421105154-0m4snkzjf.json → AI Assistant_workflow-1778504793388-ou1m1tz2x.json } +70 -266
- package/workflows/{AI Employee_workflow-1777720598005-u4cm858dv.json → AI Employee_example_workflow-1777720598005-u4cm858dv.json } +112 -112
- package/workflows/Claude Assistant_workflow-1778380124051-mdibn807c.json +709 -0
- package/client/dist/assets/ActionBar-vzPpSR77.js +0 -1
- package/client/dist/assets/ApiKeyInput-Ds7AKFe8.js +0 -1
- package/client/dist/assets/ApiKeyPanel-gfblELep.js +0 -1
- package/client/dist/assets/ApiUsageSection-BMNWTe2r.js +0 -1
- package/client/dist/assets/EmailPanel-B1Om64p5.js +0 -1
- package/client/dist/assets/OAuthPanel-CXyQYGBz.js +0 -1
- package/client/dist/assets/QrPairingPanel-BgNuI1we.js +0 -1
- package/client/dist/assets/RateLimitSection-YYK8sx1T.js +0 -1
- package/client/dist/assets/StatusCard-DuYA5hJR.js +0 -1
- package/client/dist/assets/index-D9tZfgvi.js +0 -363
- package/client/dist/assets/index-al7snTkG.css +0 -1
- package/client/src/components/credentials/providers.tsx +0 -177
- package/server/routers/google.py +0 -277
- package/server/routers/maps.py +0 -142
- package/server/routers/twitter.py +0 -365
- package/server/services/claude_code_service.py +0 -106
- package/server/services/memory.py +0 -159
- package/server/services/node_option_loaders/__init__.py +0 -77
- package/server/services/node_option_loaders/android_loaders.py +0 -55
- package/server/services/node_option_loaders/google_loaders.py +0 -97
- package/server/services/node_option_loaders/whatsapp_loaders.py +0 -69
- package/server/services/twitter_oauth.py +0 -411
- package/server/services/websocket_client.py +0 -29
- /package/server/{services/android → nodes/android/_relay}/__init__.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/broadcaster.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/manager.py +0 -0
- /package/server/{services/android → nodes/android/_relay}/protocol.py +0 -0
- /package/server/{services/browser_service.py → nodes/browser/_service.py} +0 -0
- /package/server/{services/whatsapp_service.py → nodes/whatsapp/_service.py} +0 -0
- /package/server/skills/{task_agent → assistant}/write-todos-skill/SKILL.md +0 -0
|
@@ -109,6 +109,13 @@ def detect_provider_from_model(model: str) -> str:
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
def is_model_valid_for_provider(model: str, provider: str) -> bool:
|
|
112
|
+
# Open-world providers — OpenRouter is a multi-vendor proxy, ollama
|
|
113
|
+
# and lmstudio serve user-installed local models whose names don't
|
|
114
|
+
# match any "lmstudio"/"ollama" substring. Treat as always-valid;
|
|
115
|
+
# the upstream API will 404 a genuinely missing model. See the
|
|
116
|
+
# mirror in services/ai.py for the full rationale.
|
|
117
|
+
if provider in ('openrouter', 'ollama', 'lmstudio'):
|
|
118
|
+
return True
|
|
112
119
|
cfg = PROVIDER_CONFIGS.get(provider)
|
|
113
120
|
if not cfg:
|
|
114
121
|
return True
|
|
@@ -12,8 +12,14 @@ logger = get_logger(__name__)
|
|
|
12
12
|
# Providers with dedicated SDK implementations
|
|
13
13
|
_DEDICATED_PROVIDERS = frozenset({"anthropic", "openai", "gemini", "openrouter"})
|
|
14
14
|
|
|
15
|
-
# All providers supported by native SDK path (dedicated + OpenAI-compatible via base_url)
|
|
16
|
-
|
|
15
|
+
# All providers supported by native SDK path (dedicated + OpenAI-compatible via base_url).
|
|
16
|
+
# Local servers (ollama, lmstudio) expose OpenAI-compatible /v1 endpoints, so they fall
|
|
17
|
+
# through to OpenAIProvider with base_url from llm_defaults.json — same path as deepseek/
|
|
18
|
+
# kimi/mistral. The user's custom URL rides via the existing {provider}_proxy credential.
|
|
19
|
+
NATIVE_PROVIDERS = _DEDICATED_PROVIDERS | frozenset({
|
|
20
|
+
"xai", "deepseek", "kimi", "mistral",
|
|
21
|
+
"ollama", "lmstudio",
|
|
22
|
+
})
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
def create_provider(
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Conversation-memory utilities — package entrypoint.
|
|
2
|
+
|
|
3
|
+
Reorganised from the single-file ``services/memory.py`` so each
|
|
4
|
+
storage format / concern owns its own module:
|
|
5
|
+
|
|
6
|
+
- :mod:`services.memory.markdown` — markdown parse/append/trim
|
|
7
|
+
(used by aiAgent / chatAgent / deep_agent / rlm_agent)
|
|
8
|
+
- :mod:`services.memory.jsonl` — Anthropic Messages JSONL
|
|
9
|
+
parse/append/trim (used by claude_code_agent's session bridge)
|
|
10
|
+
- :mod:`services.memory.vector_store` — per-session
|
|
11
|
+
``InMemoryVectorStore`` for long-term archival
|
|
12
|
+
- :mod:`services.memory.state` — orchestration: clear every store
|
|
13
|
+
keyed by an agent's conversational scope
|
|
14
|
+
|
|
15
|
+
Public surface is re-exported here so existing
|
|
16
|
+
``from services.memory import …`` imports keep working — the file
|
|
17
|
+
became a package in-place.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from services.memory.markdown import (
|
|
21
|
+
append_to_memory_markdown,
|
|
22
|
+
parse_memory_markdown,
|
|
23
|
+
trim_markdown_window,
|
|
24
|
+
)
|
|
25
|
+
from services.memory.vector_store import (
|
|
26
|
+
_memory_vector_stores,
|
|
27
|
+
get_memory_vector_store,
|
|
28
|
+
)
|
|
29
|
+
from services.memory.state import clear_agent_session_state
|
|
30
|
+
from services.memory.jsonl import (
|
|
31
|
+
append_message,
|
|
32
|
+
parse_jsonl,
|
|
33
|
+
trim_window,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
# Markdown helpers — used by every agent bridge (aiAgent /
|
|
38
|
+
# chatAgent / deep_agent / rlm_agent / claude_code_agent).
|
|
39
|
+
"parse_memory_markdown",
|
|
40
|
+
"append_to_memory_markdown",
|
|
41
|
+
"trim_markdown_window",
|
|
42
|
+
# Vector-store helpers.
|
|
43
|
+
"_memory_vector_stores",
|
|
44
|
+
"get_memory_vector_store",
|
|
45
|
+
# Orchestration.
|
|
46
|
+
"clear_agent_session_state",
|
|
47
|
+
# JSONL helpers — standalone primitive, not currently used by any
|
|
48
|
+
# agent bridge but tested + kept for future SDK migration.
|
|
49
|
+
"parse_jsonl",
|
|
50
|
+
"append_message",
|
|
51
|
+
"trim_window",
|
|
52
|
+
]
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Standard-JSONL conversation-memory helpers.
|
|
2
|
+
|
|
3
|
+
Lines are Anthropic Messages API objects: ``{"role": "user"|"assistant",
|
|
4
|
+
"content": str | List[ContentBlock], ...}``. Extra metadata
|
|
5
|
+
(``timestamp``, ``session_id``, ``model``, ...) rides alongside
|
|
6
|
+
``role`` / ``content`` and is preserved on round-trip; standard parsers
|
|
7
|
+
(Anthropic SDK, LangChain converters) ignore unknown keys.
|
|
8
|
+
|
|
9
|
+
Used by :mod:`nodes.agent.claude_code_agent` (session memory) via
|
|
10
|
+
:func:`services.cli_agent.service.AICliService.run_batch`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from typing import Any, List, Tuple
|
|
15
|
+
|
|
16
|
+
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_jsonl(text: str) -> List[BaseMessage]:
|
|
20
|
+
"""Standard JSONL -> LangChain ``BaseMessage`` list.
|
|
21
|
+
|
|
22
|
+
Tool-call content blocks collapse to text; rows with unknown roles
|
|
23
|
+
or unparseable JSON are skipped (forward compatibility).
|
|
24
|
+
"""
|
|
25
|
+
if not text:
|
|
26
|
+
return []
|
|
27
|
+
out: List[BaseMessage] = []
|
|
28
|
+
for line in text.splitlines():
|
|
29
|
+
line = line.strip()
|
|
30
|
+
if not line:
|
|
31
|
+
continue
|
|
32
|
+
try:
|
|
33
|
+
obj = json.loads(line)
|
|
34
|
+
except json.JSONDecodeError:
|
|
35
|
+
continue
|
|
36
|
+
role = obj.get("role")
|
|
37
|
+
content = obj.get("content")
|
|
38
|
+
if isinstance(content, list):
|
|
39
|
+
content = " ".join(
|
|
40
|
+
blk.get("text", "") if isinstance(blk, dict) else str(blk)
|
|
41
|
+
for blk in content
|
|
42
|
+
if isinstance(blk, dict) and blk.get("type") == "text"
|
|
43
|
+
)
|
|
44
|
+
if not isinstance(content, str):
|
|
45
|
+
continue
|
|
46
|
+
if role == "user":
|
|
47
|
+
out.append(HumanMessage(content=content))
|
|
48
|
+
elif role == "assistant":
|
|
49
|
+
out.append(AIMessage(content=content))
|
|
50
|
+
return out
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def append_message(
|
|
54
|
+
text: str, role: str, content: str, **metadata: Any,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Append one Anthropic Messages-format line to a JSONL string.
|
|
57
|
+
|
|
58
|
+
Metadata fields ride alongside ``role`` / ``content``. Always emits
|
|
59
|
+
a trailing newline so successive appends concatenate cleanly.
|
|
60
|
+
"""
|
|
61
|
+
line = json.dumps(
|
|
62
|
+
{"role": role, "content": content, **metadata}, ensure_ascii=False,
|
|
63
|
+
)
|
|
64
|
+
if text and not text.endswith("\n"):
|
|
65
|
+
text = text + "\n"
|
|
66
|
+
return (text or "") + line + "\n"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def trim_window(text: str, window_size: int) -> Tuple[str, List[str]]:
|
|
70
|
+
"""Keep the last ``window_size * 2`` lines (~ N user/assistant
|
|
71
|
+
pairs). Returns ``(trimmed, removed)``. Removed lines are returned
|
|
72
|
+
verbatim so callers can hand them to the long-term vector store.
|
|
73
|
+
"""
|
|
74
|
+
lines = [ln for ln in (text or "").splitlines() if ln.strip()]
|
|
75
|
+
keep = window_size * 2
|
|
76
|
+
if len(lines) <= keep:
|
|
77
|
+
return text, []
|
|
78
|
+
removed = lines[:-keep]
|
|
79
|
+
trimmed = "\n".join(lines[-keep:]) + "\n"
|
|
80
|
+
return trimmed, removed
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Markdown-based conversation-memory helpers.
|
|
2
|
+
|
|
3
|
+
Parse, append, and trim conversation history stored in the bespoke
|
|
4
|
+
``### **Human/Assistant** (timestamp)`` markdown format. Used by
|
|
5
|
+
aiAgent, chatAgent, deep_agent, and rlm_agent for persistent memory
|
|
6
|
+
across turns. The claude_code_agent bridge uses the JSONL helpers in
|
|
7
|
+
:mod:`services.memory.jsonl` instead.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_memory_markdown(content: str) -> List[BaseMessage]:
|
|
18
|
+
"""Parse markdown memory content into LangChain messages.
|
|
19
|
+
|
|
20
|
+
Markdown format:
|
|
21
|
+
### **Human** (timestamp)
|
|
22
|
+
message content
|
|
23
|
+
|
|
24
|
+
### **Assistant** (timestamp)
|
|
25
|
+
response content
|
|
26
|
+
"""
|
|
27
|
+
messages: List[BaseMessage] = []
|
|
28
|
+
pattern = r'### \*\*(Human|Assistant)\*\*[^\n]*\n(.*?)(?=\n### \*\*|$)'
|
|
29
|
+
for role, text in re.findall(pattern, content, re.DOTALL):
|
|
30
|
+
text = text.strip()
|
|
31
|
+
if text:
|
|
32
|
+
msg_class = HumanMessage if role == "Human" else AIMessage
|
|
33
|
+
messages.append(msg_class(content=text))
|
|
34
|
+
return messages
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def append_to_memory_markdown(content: str, role: str, message: str) -> str:
|
|
38
|
+
"""Append a message to markdown memory content."""
|
|
39
|
+
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
40
|
+
label = "Human" if role == "human" else "Assistant"
|
|
41
|
+
entry = f"\n### **{label}** ({ts})\n{message}\n"
|
|
42
|
+
# Remove the empty-state placeholder if present.
|
|
43
|
+
return content.replace("*No messages yet.*\n", "") + entry
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def trim_markdown_window(content: str, window_size: int) -> tuple:
|
|
47
|
+
"""Keep the last ``window_size`` message PAIRS; return
|
|
48
|
+
``(trimmed_content, removed_texts)``. Removed texts are returned
|
|
49
|
+
so callers can hand them to the long-term vector store."""
|
|
50
|
+
pattern = r'(### \*\*(Human|Assistant)\*\*[^\n]*\n.*?)(?=\n### \*\*|$)'
|
|
51
|
+
blocks = [m[0] for m in re.findall(pattern, content, re.DOTALL)]
|
|
52
|
+
|
|
53
|
+
if len(blocks) <= window_size * 2:
|
|
54
|
+
return content, []
|
|
55
|
+
|
|
56
|
+
keep = blocks[-(window_size * 2):]
|
|
57
|
+
removed = blocks[:-(window_size * 2)]
|
|
58
|
+
|
|
59
|
+
removed_texts: List[str] = []
|
|
60
|
+
for block in removed:
|
|
61
|
+
match = re.search(r"\n(.*)$", block, re.DOTALL)
|
|
62
|
+
if match:
|
|
63
|
+
removed_texts.append(match.group(1).strip())
|
|
64
|
+
|
|
65
|
+
return "# Conversation History\n" + "\n".join(keep), removed_texts
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Cross-store agent-session state-clear orchestration.
|
|
2
|
+
|
|
3
|
+
"Memory" from the user's perspective is not just the markdown
|
|
4
|
+
transcript — it's every piece of state an agent reuses across iterations
|
|
5
|
+
of a conversation. ``simpleMemory.memory_content`` is the visible part;
|
|
6
|
+
the long-term vector store and ``TodoService`` plan-work-update lists
|
|
7
|
+
are the invisible parts that quietly bloat subsequent runs (notably
|
|
8
|
+
``task_agent``, whose default skill bundle instructs the LLM to read
|
|
9
|
+
accumulated todos every run).
|
|
10
|
+
|
|
11
|
+
When a ``memory_node_id`` is provided, the simpleMemory node's own
|
|
12
|
+
fields (``memory_content``, ``memory_jsonl``, ``last_session_id``) are
|
|
13
|
+
also reset to defaults via ``database.save_node_parameters`` so the
|
|
14
|
+
claude_code_agent JSONL bridge surface clears alongside.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from typing import Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from core.logging import get_logger
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
DEFAULT_MEMORY_CONTENT = "# Conversation History\n\n*No messages yet.*\n"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def clear_agent_session_state(
|
|
28
|
+
session_id: str,
|
|
29
|
+
workflow_id: str = None,
|
|
30
|
+
clear_long_term: bool = False,
|
|
31
|
+
memory_node_id: Optional[str] = None,
|
|
32
|
+
) -> Dict[str, Any]:
|
|
33
|
+
"""Clear every store keyed by an agent's conversational scope.
|
|
34
|
+
|
|
35
|
+
``TodoService`` is keyed by ``ctx.workflow_id or ctx.node_id or
|
|
36
|
+
"default"`` (see ``server/nodes/tool/write_todos.py``). We clear all
|
|
37
|
+
three candidate keys to match whichever fallback the agent actually
|
|
38
|
+
used at write time.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
session_id: ``simpleMemory`` node's ``session_id`` parameter.
|
|
42
|
+
workflow_id: Active workflow id (passed by the frontend so we
|
|
43
|
+
can clear ``TodoService`` entries written under it).
|
|
44
|
+
clear_long_term: When ``True``, drop the per-session vector
|
|
45
|
+
store too.
|
|
46
|
+
memory_node_id: When provided, reset the simpleMemory node's
|
|
47
|
+
``memory_content`` to the default placeholder and wipe
|
|
48
|
+
``memory_jsonl`` + ``last_session_id``. Without this the
|
|
49
|
+
JSONL bridge fields stay populated and ``--resume`` retries
|
|
50
|
+
stale UUIDs. Frontend-driven legacy clears omit this and
|
|
51
|
+
handle markdown reset client-side.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
Dict with ``cleared_vector_store`` (bool), ``cleared_todo_keys``
|
|
55
|
+
(list[str]), and ``cleared_memory_node`` (bool) for
|
|
56
|
+
caller-visible diagnostics.
|
|
57
|
+
"""
|
|
58
|
+
# The live vector-store cache lives in ``services.ai`` (the dict in
|
|
59
|
+
# ``services.memory.vector_store`` is dormant — future cleanup will
|
|
60
|
+
# consolidate). Lazy import keeps ``services.ai``'s heavy LangChain
|
|
61
|
+
# deps off the hot path.
|
|
62
|
+
from services.ai import _memory_vector_stores as _live_vector_stores
|
|
63
|
+
from services.todo_service import get_todo_service
|
|
64
|
+
|
|
65
|
+
cleared_vector_store = False
|
|
66
|
+
if clear_long_term and session_id in _live_vector_stores:
|
|
67
|
+
del _live_vector_stores[session_id]
|
|
68
|
+
cleared_vector_store = True
|
|
69
|
+
logger.info(f"[Memory] Cleared vector store for session '{session_id}'")
|
|
70
|
+
|
|
71
|
+
todo_service = get_todo_service()
|
|
72
|
+
cleared_todo_keys: List[str] = []
|
|
73
|
+
seen = set()
|
|
74
|
+
for key in (workflow_id, session_id, "default"):
|
|
75
|
+
if key and key not in seen:
|
|
76
|
+
seen.add(key)
|
|
77
|
+
todo_service.clear(key)
|
|
78
|
+
cleared_todo_keys.append(key)
|
|
79
|
+
|
|
80
|
+
cleared_memory_node = False
|
|
81
|
+
if memory_node_id:
|
|
82
|
+
from services.plugin.deps import get_database
|
|
83
|
+
|
|
84
|
+
db = get_database()
|
|
85
|
+
params = await db.get_node_parameters(memory_node_id) or {}
|
|
86
|
+
params["memory_content"] = DEFAULT_MEMORY_CONTENT
|
|
87
|
+
# Wipe `last_session_id` so claude_code_agent starts a fresh
|
|
88
|
+
# session on next spawn instead of `--resume`-ing into the now-
|
|
89
|
+
# cleared transcript. Drop any orphan JSONL field from the
|
|
90
|
+
# earlier bridge revision.
|
|
91
|
+
params["last_session_id"] = None
|
|
92
|
+
params.pop("memory_jsonl", None)
|
|
93
|
+
await db.save_node_parameters(memory_node_id, params)
|
|
94
|
+
cleared_memory_node = True
|
|
95
|
+
logger.info(
|
|
96
|
+
"[Memory] Cleared simpleMemory node fields memory_node=%s "
|
|
97
|
+
"(memory_content reset + last_session_id wiped)",
|
|
98
|
+
memory_node_id,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
logger.info(
|
|
102
|
+
"[Memory] Cleared agent session state session=%s workflow_id=%s "
|
|
103
|
+
"vector_store=%s todo_keys=%s memory_node=%s",
|
|
104
|
+
session_id, workflow_id, cleared_vector_store, cleared_todo_keys,
|
|
105
|
+
cleared_memory_node,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"cleared_vector_store": cleared_vector_store,
|
|
110
|
+
"cleared_todo_keys": cleared_todo_keys,
|
|
111
|
+
"cleared_memory_node": cleared_memory_node,
|
|
112
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Per-session ``InMemoryVectorStore`` for long-term memory archival.
|
|
2
|
+
|
|
3
|
+
Note: a duplicate dict ``_memory_vector_stores`` lives in
|
|
4
|
+
:mod:`services.ai`. ``services.memory.state.clear_agent_session_state``
|
|
5
|
+
clears the live (``services.ai``) one — this module's dict is an
|
|
6
|
+
intentional second copy that future cleanup will consolidate. Keep both
|
|
7
|
+
for now; the documented duplication is preserved verbatim.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
from core.logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Per-``session_id`` cache. The live cache used by aiAgent etc. lives
|
|
18
|
+
# in ``services.ai``; this dict is the package-private counterpart for
|
|
19
|
+
# JSONL-bridge consumers and is referenced by state-clear orchestration.
|
|
20
|
+
_memory_vector_stores: Dict[str, Any] = {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_memory_vector_store(session_id: str):
|
|
24
|
+
"""Get or create an ``InMemoryVectorStore`` for a session.
|
|
25
|
+
|
|
26
|
+
Returns ``None`` (with a warning) when the optional LangChain
|
|
27
|
+
HuggingFace dep is missing — long-term archival is best-effort.
|
|
28
|
+
"""
|
|
29
|
+
if session_id not in _memory_vector_stores:
|
|
30
|
+
try:
|
|
31
|
+
from langchain_core.vectorstores import InMemoryVectorStore
|
|
32
|
+
from langchain_huggingface import HuggingFaceEmbeddings
|
|
33
|
+
|
|
34
|
+
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-en-v1.5")
|
|
35
|
+
_memory_vector_stores[session_id] = InMemoryVectorStore(embeddings)
|
|
36
|
+
logger.debug(f"[Memory] Created vector store for session '{session_id}'")
|
|
37
|
+
except ImportError as exc:
|
|
38
|
+
logger.warning(f"[Memory] Vector store not available: {exc}")
|
|
39
|
+
return None
|
|
40
|
+
return _memory_vector_stores[session_id]
|
|
@@ -235,6 +235,82 @@ class ModelRegistryService:
|
|
|
235
235
|
|
|
236
236
|
return None
|
|
237
237
|
|
|
238
|
+
def register_local_model(
|
|
239
|
+
self,
|
|
240
|
+
provider: str,
|
|
241
|
+
model_id: str,
|
|
242
|
+
params: Dict[str, Any],
|
|
243
|
+
) -> None:
|
|
244
|
+
"""Register an Ollama / LM Studio model with its actual params.
|
|
245
|
+
|
|
246
|
+
Called from ``validate_local_llm`` after the official SDK probe
|
|
247
|
+
(``ollama.AsyncClient.ps()`` / ``lmstudio.AsyncClient.llm.list_loaded()``)
|
|
248
|
+
returns each currently-loaded model with its typed params:
|
|
249
|
+
``context_length`` (live n_ctx the server enforces),
|
|
250
|
+
``vision`` / ``supports_tools`` capability flags, and metadata
|
|
251
|
+
(``architecture``, ``param_size``, ``quantization``). The sync
|
|
252
|
+
``get_context_length`` / ``get_max_output_tokens`` lookups find
|
|
253
|
+
this entry first, so chat / agent execution honour the real
|
|
254
|
+
n_ctx the server is serving — no JSON guess, no string parsing.
|
|
255
|
+
|
|
256
|
+
Stored under the same ``{provider}/{local_id}`` key shape as
|
|
257
|
+
cloud models so ``get_model_info``'s waterfall matches without
|
|
258
|
+
any per-provider branching. ``provider`` is canonical
|
|
259
|
+
(``ollama`` / ``lmstudio``).
|
|
260
|
+
|
|
261
|
+
Idempotent: a re-validation overwrites the prior entry.
|
|
262
|
+
|
|
263
|
+
Persisted to the same ``model_registry.json`` cache the
|
|
264
|
+
OpenRouter refresh writes — the entry survives a server
|
|
265
|
+
restart, so the user doesn't have to re-click "Fetch" every
|
|
266
|
+
time the process bounces just to keep the n_ctx context
|
|
267
|
+
registered for their local model.
|
|
268
|
+
"""
|
|
269
|
+
ctx = int(params.get("context_length") or 0)
|
|
270
|
+
max_out = int(params.get("max_output_tokens") or 0)
|
|
271
|
+
# Default max_output to 25% of context (OpenRouter convention for
|
|
272
|
+
# local models), capped at 4096 — local backends rarely benefit
|
|
273
|
+
# from larger budgets and overcommitting eats into the prompt.
|
|
274
|
+
if not max_out and ctx:
|
|
275
|
+
max_out = min(4096, max(512, ctx // 4))
|
|
276
|
+
|
|
277
|
+
# Capability flags + metadata from the SDK probe go into
|
|
278
|
+
# `supported_parameters` so they survive the JSON roundtrip via
|
|
279
|
+
# `ModelInfo.to_dict` / `from_dict`. Cloud models populate this
|
|
280
|
+
# from OpenRouter's listing; for locals we use it as a typed
|
|
281
|
+
# capability bag so downstream code (tool-binding, vision UI
|
|
282
|
+
# gating) can read the same fields regardless of provider.
|
|
283
|
+
supported: List[str] = []
|
|
284
|
+
if params.get("supports_tools"):
|
|
285
|
+
supported.append("tools")
|
|
286
|
+
if params.get("vision"):
|
|
287
|
+
supported.append("vision")
|
|
288
|
+
|
|
289
|
+
# Match the user-friendly temperature default for OpenAI-compat
|
|
290
|
+
# local servers (0..2 range — same as the JSON default).
|
|
291
|
+
info = ModelInfo(
|
|
292
|
+
id=f"{provider}/{model_id}",
|
|
293
|
+
name=model_id,
|
|
294
|
+
provider=provider,
|
|
295
|
+
local_id=model_id,
|
|
296
|
+
context_length=ctx,
|
|
297
|
+
max_output_tokens=max_out,
|
|
298
|
+
temperature_range=(0.0, 2.0),
|
|
299
|
+
supported_parameters=supported,
|
|
300
|
+
)
|
|
301
|
+
self._models[f"{provider}/{model_id}"] = info
|
|
302
|
+
logger.info(
|
|
303
|
+
"[%s] registered model %s (ctx=%s, max_out=%s, tools=%s, vision=%s)",
|
|
304
|
+
provider, model_id, ctx, max_out,
|
|
305
|
+
params.get("supports_tools", False), params.get("vision", False),
|
|
306
|
+
)
|
|
307
|
+
# Persist so the entry survives process restart.
|
|
308
|
+
try:
|
|
309
|
+
self._save_cache()
|
|
310
|
+
except Exception as e: # noqa: BLE001 — best-effort persistence
|
|
311
|
+
logger.warning("Failed to persist local model entry %s/%s: %s",
|
|
312
|
+
provider, model_id, e)
|
|
313
|
+
|
|
238
314
|
def get_max_output_tokens(self, model: str, provider: str) -> int:
|
|
239
315
|
"""Get max output tokens: registry -> llm_defaults -> 4096."""
|
|
240
316
|
info = self.get_model_info(model, provider)
|
|
@@ -29,32 +29,88 @@ class NodeAllowlistService:
|
|
|
29
29
|
|
|
30
30
|
Response shape:
|
|
31
31
|
show_all: bool
|
|
32
|
-
true -> do not filter the palette; every node is visible
|
|
32
|
+
true -> do not filter the palette; every node is visible
|
|
33
|
+
(still subject to disabled_groups + disabled_nodes).
|
|
33
34
|
false -> show only node types listed in enabled_nodes.
|
|
34
35
|
enabled_nodes: list[str]
|
|
35
36
|
Only meaningful when show_all is false.
|
|
37
|
+
disabled_groups: list[str]
|
|
38
|
+
Absolute blocklist. A node whose first group matches any
|
|
39
|
+
entry here is hidden in BOTH normal and dev mode, even
|
|
40
|
+
if listed in enabled_nodes. Use to disable an entire
|
|
41
|
+
backend group (e.g. 'android' hides all 16 Android
|
|
42
|
+
service nodes + androidTool).
|
|
43
|
+
disabled_nodes: list[str]
|
|
44
|
+
Absolute blocklist by exact node-type identifier. Same
|
|
45
|
+
mode-independent enforcement as disabled_groups; use
|
|
46
|
+
for one-off types whose group label doesn't match
|
|
47
|
+
(e.g. 'android_agent' belongs to the 'agent' group).
|
|
48
|
+
disabled_credential_categories: list[str]
|
|
49
|
+
Absolute blocklist for the Credentials Modal — every
|
|
50
|
+
provider whose `category` matches an entry here is
|
|
51
|
+
hidden from the modal AND its category header is
|
|
52
|
+
stripped. Use to disable an entire credential category
|
|
53
|
+
(e.g. 'android' hides the Android relay panel + the
|
|
54
|
+
Android category header). Mirrors disabled_groups
|
|
55
|
+
semantically — the same conceptual entity (android
|
|
56
|
+
feature surface) but the credential catalogue uses its
|
|
57
|
+
own category taxonomy independent of node groups.
|
|
58
|
+
disabled_skill_folders: list[str]
|
|
59
|
+
Absolute blocklist for the Master Skill folder
|
|
60
|
+
dropdown — every entry hides the matching subfolder
|
|
61
|
+
under server/skills/. Use when disabling a feature
|
|
62
|
+
that also ships its own skill folder (e.g.
|
|
63
|
+
'android_agent' so users can't see the 12 android-
|
|
64
|
+
tied skills when android nodes are disabled).
|
|
65
|
+
Email has no dedicated skill folder (email-tied skills
|
|
66
|
+
live under productivity_agent mixed with Google
|
|
67
|
+
Workspace) so no email entry is needed.
|
|
36
68
|
"""
|
|
69
|
+
defaults = {
|
|
70
|
+
"show_all": True,
|
|
71
|
+
"enabled_nodes": [],
|
|
72
|
+
"disabled_groups": [],
|
|
73
|
+
"disabled_nodes": [],
|
|
74
|
+
"disabled_credential_categories": [],
|
|
75
|
+
"disabled_skill_folders": [],
|
|
76
|
+
}
|
|
77
|
+
|
|
37
78
|
if not self._config_path.exists():
|
|
38
|
-
return
|
|
79
|
+
return defaults
|
|
39
80
|
|
|
40
81
|
try:
|
|
41
82
|
with self._config_path.open("r", encoding="utf-8") as f:
|
|
42
83
|
raw = json.load(f)
|
|
43
84
|
except Exception as e:
|
|
44
85
|
logger.warning("Failed to parse node_allowlist.json, falling back to show_all: %s", e)
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
|
|
86
|
+
return defaults
|
|
87
|
+
|
|
88
|
+
def _str_list(key: str) -> List[str]:
|
|
89
|
+
value = raw.get(key, [])
|
|
90
|
+
if not isinstance(value, list):
|
|
91
|
+
logger.warning(
|
|
92
|
+
"node_allowlist.json '%s' is not a list, treating as empty",
|
|
93
|
+
key,
|
|
94
|
+
)
|
|
95
|
+
return []
|
|
96
|
+
return [n for n in value if isinstance(n, str)]
|
|
97
|
+
|
|
98
|
+
enabled_nodes = _str_list("enabled_nodes")
|
|
99
|
+
disabled_groups = _str_list("disabled_groups")
|
|
100
|
+
disabled_nodes = _str_list("disabled_nodes")
|
|
101
|
+
disabled_credential_categories = _str_list("disabled_credential_categories")
|
|
102
|
+
disabled_skill_folders = _str_list("disabled_skill_folders")
|
|
103
|
+
|
|
104
|
+
show_all = len(enabled_nodes) == 0
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
"show_all": show_all,
|
|
108
|
+
"enabled_nodes": enabled_nodes,
|
|
109
|
+
"disabled_groups": disabled_groups,
|
|
110
|
+
"disabled_nodes": disabled_nodes,
|
|
111
|
+
"disabled_credential_categories": disabled_credential_categories,
|
|
112
|
+
"disabled_skill_folders": disabled_skill_folders,
|
|
113
|
+
}
|
|
58
114
|
|
|
59
115
|
|
|
60
116
|
_instance: NodeAllowlistService | None = None
|
|
@@ -33,9 +33,9 @@ if TYPE_CHECKING:
|
|
|
33
33
|
from core.config import Settings
|
|
34
34
|
from core.database import Database
|
|
35
35
|
from services.ai import AIService
|
|
36
|
-
from
|
|
36
|
+
from nodes.location._service import MapsService
|
|
37
37
|
from services.text import TextService
|
|
38
|
-
from
|
|
38
|
+
from nodes.android._dispatcher import AndroidService
|
|
39
39
|
|
|
40
40
|
logger = get_logger(__name__)
|
|
41
41
|
|
|
@@ -866,6 +866,26 @@ def list_node_types_with_schema() -> list[str]:
|
|
|
866
866
|
return sorted(NODE_OUTPUT_SCHEMAS.keys())
|
|
867
867
|
|
|
868
868
|
|
|
869
|
+
from services.plugin.registry import IdempotentRegistry as _IdempotentRegistry # noqa: E402
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def _bust_schema_cache(node_type: str, _model_class: type[BaseModel]) -> None:
|
|
873
|
+
"""on_register hook: drop the cached JSON schema for the re-registered type."""
|
|
874
|
+
_schema_cache.pop(node_type, None)
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
# Backed by the module-level NODE_OUTPUT_SCHEMAS dict so existing
|
|
878
|
+
# readers (e.g. get_output_schema, list_node_types_with_schema, tests)
|
|
879
|
+
# keep working.
|
|
880
|
+
_OUTPUT_SCHEMA_REGISTRY: _IdempotentRegistry[str, type[BaseModel]] = (
|
|
881
|
+
_IdempotentRegistry(
|
|
882
|
+
"output_schema",
|
|
883
|
+
items=NODE_OUTPUT_SCHEMAS,
|
|
884
|
+
on_register=_bust_schema_cache,
|
|
885
|
+
)
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
|
|
869
889
|
def register_output_schema(node_type: str, model_class: type[BaseModel]) -> None:
|
|
870
890
|
"""Publish an output schema for a node type from a plugin package.
|
|
871
891
|
|
|
@@ -877,13 +897,4 @@ def register_output_schema(node_type: str, model_class: type[BaseModel]) -> None
|
|
|
877
897
|
|
|
878
898
|
See e.g. ``nodes/telegram/__init__.py``.
|
|
879
899
|
"""
|
|
880
|
-
|
|
881
|
-
if existing is not None and existing is not model_class:
|
|
882
|
-
raise ValueError(
|
|
883
|
-
f"Output schema for '{node_type}' is already registered as "
|
|
884
|
-
f"{existing.__module__}.{existing.__qualname__}; refusing to "
|
|
885
|
-
f"overwrite with {model_class.__module__}.{model_class.__qualname__}"
|
|
886
|
-
)
|
|
887
|
-
NODE_OUTPUT_SCHEMAS[node_type] = model_class
|
|
888
|
-
# Bust the JSON-schema cache so the next call re-serialises.
|
|
889
|
-
_schema_cache.pop(node_type, None)
|
|
900
|
+
_OUTPUT_SCHEMA_REGISTRY.register(node_type, model_class)
|
|
@@ -85,7 +85,7 @@ def get_node_spec(node_type: str) -> Optional[dict[str, Any]]:
|
|
|
85
85
|
# Wave 10.A — full visual contract. Only emit fields when seeded so the
|
|
86
86
|
# wire format stays compact and unseeded types keep the pre-10 shape.
|
|
87
87
|
for key in ("color", "componentKind", "handles", "credentials",
|
|
88
|
-
"hideOutputHandle", "visibility"):
|
|
88
|
+
"hideOutputHandle", "hideInputHandle", "visibility"):
|
|
89
89
|
if key in meta:
|
|
90
90
|
spec[key] = meta[key]
|
|
91
91
|
|
|
@@ -11,7 +11,7 @@ base URL (scheme + host + port) comes from the connection itself.
|
|
|
11
11
|
|
|
12
12
|
from urllib.parse import urlparse
|
|
13
13
|
|
|
14
|
-
from
|
|
14
|
+
from nodes.google._oauth import get_callback_paths
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def get_base_url(connection) -> str:
|
|
@@ -28,6 +28,7 @@ from __future__ import annotations
|
|
|
28
28
|
from services.plugin.base import BaseNode, NodeUserError
|
|
29
29
|
from services.plugin.action import ActionNode
|
|
30
30
|
from services.plugin.trigger import TriggerNode
|
|
31
|
+
from services.plugin.polling import PollingTriggerNode
|
|
31
32
|
from services.plugin.tool import ToolNode
|
|
32
33
|
from services.plugin.operation import Operation, OperationSpec
|
|
33
34
|
from services.plugin.routing import Routing, RoutingRequest, RoutingOutput, execute_routing
|
|
@@ -52,6 +53,7 @@ __all__ = [
|
|
|
52
53
|
"NodeUserError",
|
|
53
54
|
"ActionNode",
|
|
54
55
|
"TriggerNode",
|
|
56
|
+
"PollingTriggerNode",
|
|
55
57
|
"ToolNode",
|
|
56
58
|
"Operation",
|
|
57
59
|
"OperationSpec",
|