flowent 0.0.1 → 0.0.4
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 +19 -8
- package/backend/.python-version +1 -0
- package/backend/pyproject.toml +57 -0
- package/backend/src/flowent/__init__.py +3 -0
- package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/stats_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
- package/backend/src/flowent/_version.py +7 -0
- package/backend/src/flowent/access.py +247 -0
- package/backend/src/flowent/agent.py +2808 -0
- package/backend/src/flowent/assistant_commands.py +106 -0
- package/backend/src/flowent/channels/__init__.py +3 -0
- package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
- package/backend/src/flowent/channels/telegram.py +615 -0
- package/backend/src/flowent/cli.py +85 -0
- package/backend/src/flowent/config.py +14 -0
- package/backend/src/flowent/dev.py +3 -0
- package/backend/src/flowent/events.py +157 -0
- package/backend/src/flowent/graph_runtime.py +60 -0
- package/backend/src/flowent/graph_service.py +1346 -0
- package/backend/src/flowent/image_assets.py +356 -0
- package/backend/src/flowent/logging.py +155 -0
- package/backend/src/flowent/main.py +124 -0
- package/backend/src/flowent/mcp_service.py +1904 -0
- package/backend/src/flowent/model_metadata.py +98 -0
- package/backend/src/flowent/models/__init__.py +121 -0
- package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/agent.py +33 -0
- package/backend/src/flowent/models/base.py +24 -0
- package/backend/src/flowent/models/blueprint.py +176 -0
- package/backend/src/flowent/models/content.py +164 -0
- package/backend/src/flowent/models/delta.py +44 -0
- package/backend/src/flowent/models/event.py +51 -0
- package/backend/src/flowent/models/graph.py +437 -0
- package/backend/src/flowent/models/history.py +214 -0
- package/backend/src/flowent/models/llm.py +61 -0
- package/backend/src/flowent/models/message.py +27 -0
- package/backend/src/flowent/models/tab.py +48 -0
- package/backend/src/flowent/models/todo.py +10 -0
- package/backend/src/flowent/network.py +146 -0
- package/backend/src/flowent/prompts/__init__.py +67 -0
- package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/common.py +250 -0
- package/backend/src/flowent/prompts/steward.py +64 -0
- package/backend/src/flowent/providers/__init__.py +23 -0
- package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/anthropic.py +468 -0
- package/backend/src/flowent/providers/base_url.py +60 -0
- package/backend/src/flowent/providers/configuration.py +182 -0
- package/backend/src/flowent/providers/content.py +122 -0
- package/backend/src/flowent/providers/errors.py +223 -0
- package/backend/src/flowent/providers/gateway.py +169 -0
- package/backend/src/flowent/providers/gemini.py +447 -0
- package/backend/src/flowent/providers/headers.py +20 -0
- package/backend/src/flowent/providers/management.py +96 -0
- package/backend/src/flowent/providers/ollama.py +293 -0
- package/backend/src/flowent/providers/openai.py +422 -0
- package/backend/src/flowent/providers/openai_responses.py +655 -0
- package/backend/src/flowent/providers/registry.py +144 -0
- package/backend/src/flowent/providers/sse.py +31 -0
- package/backend/src/flowent/providers/thinking.py +79 -0
- package/backend/src/flowent/registry.py +73 -0
- package/backend/src/flowent/role_management.py +255 -0
- package/backend/src/flowent/routes/__init__.py +30 -0
- package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/stats.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/access.py +48 -0
- package/backend/src/flowent/routes/assistant.py +155 -0
- package/backend/src/flowent/routes/image_assets.py +33 -0
- package/backend/src/flowent/routes/mcp.py +125 -0
- package/backend/src/flowent/routes/meta.py +28 -0
- package/backend/src/flowent/routes/nodes.py +365 -0
- package/backend/src/flowent/routes/prompts.py +46 -0
- package/backend/src/flowent/routes/providers_route.py +364 -0
- package/backend/src/flowent/routes/roles.py +207 -0
- package/backend/src/flowent/routes/settings.py +324 -0
- package/backend/src/flowent/routes/stats.py +229 -0
- package/backend/src/flowent/routes/tabs.py +292 -0
- package/backend/src/flowent/routes/ws.py +33 -0
- package/backend/src/flowent/runtime.py +188 -0
- package/backend/src/flowent/sandbox.py +45 -0
- package/backend/src/flowent/security.py +42 -0
- package/backend/src/flowent/settings.py +2467 -0
- package/backend/src/flowent/settings_management.py +286 -0
- package/backend/src/flowent/state_db.py +120 -0
- package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +1 -0
- package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +1 -0
- package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +3 -0
- package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +7 -0
- package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +1 -0
- package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +1 -0
- package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +3 -0
- package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +1 -0
- package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +3 -0
- package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +1 -0
- package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +1 -0
- package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
- package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
- package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +1 -0
- package/backend/src/flowent/static/assets/badge-74-3jsCg.js +1 -0
- package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +1 -0
- package/backend/src/flowent/static/assets/datetime-m6_O_Ci9.js +1 -0
- package/backend/src/flowent/static/assets/dialog-BeGSweF6.js +1 -0
- package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
- package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
- package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +1 -0
- package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +10 -0
- package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
- package/backend/src/flowent/static/assets/markdown-vendor-DVdy_w12.js +29 -0
- package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +1 -0
- package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
- package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +1 -0
- package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
- package/backend/src/flowent/static/assets/select-DL_LPeDj.js +1 -0
- package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +1 -0
- package/backend/src/flowent/static/assets/triState-DEr3NkXV.js +1 -0
- package/backend/src/flowent/static/assets/ui-vendor-Dg9NNnWX.js +51 -0
- package/backend/src/flowent/static/index.html +36 -0
- package/backend/src/flowent/stats_service.py +218 -0
- package/backend/src/flowent/tools/__init__.py +201 -0
- package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/connect.py +156 -0
- package/backend/src/flowent/tools/contacts.py +22 -0
- package/backend/src/flowent/tools/create_agent.py +270 -0
- package/backend/src/flowent/tools/create_tab.py +59 -0
- package/backend/src/flowent/tools/delete_tab.py +39 -0
- package/backend/src/flowent/tools/edit.py +142 -0
- package/backend/src/flowent/tools/exec.py +117 -0
- package/backend/src/flowent/tools/fetch.py +85 -0
- package/backend/src/flowent/tools/idle.py +27 -0
- package/backend/src/flowent/tools/list_roles.py +50 -0
- package/backend/src/flowent/tools/list_tabs.py +96 -0
- package/backend/src/flowent/tools/list_tools.py +24 -0
- package/backend/src/flowent/tools/manage_prompts.py +102 -0
- package/backend/src/flowent/tools/manage_providers.py +220 -0
- package/backend/src/flowent/tools/manage_roles.py +275 -0
- package/backend/src/flowent/tools/manage_settings.py +346 -0
- package/backend/src/flowent/tools/mcp.py +199 -0
- package/backend/src/flowent/tools/read.py +152 -0
- package/backend/src/flowent/tools/send.py +50 -0
- package/backend/src/flowent/tools/set_permissions.py +84 -0
- package/backend/src/flowent/tools/sleep.py +41 -0
- package/backend/src/flowent/tools/todo.py +51 -0
- package/backend/src/flowent/workspace_store.py +479 -0
- package/backend/tests/__init__.py +0 -0
- package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/conftest.py +6 -0
- package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/conftest.py +29 -0
- package/backend/tests/integration/api/test_access_api.py +182 -0
- package/backend/tests/integration/api/test_assistant_api.py +354 -0
- package/backend/tests/integration/api/test_frontend_mounting.py +61 -0
- package/backend/tests/integration/api/test_mcp_api.py +116 -0
- package/backend/tests/integration/api/test_meta_api.py +33 -0
- package/backend/tests/integration/api/test_nodes_api.py +486 -0
- package/backend/tests/integration/api/test_prompts_api.py +47 -0
- package/backend/tests/integration/api/test_roles_api.py +227 -0
- package/backend/tests/integration/api/test_tabs_api.py +501 -0
- package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/test_agent_public_api.py +746 -0
- package/backend/tests/unit/agent/test_agent_runtime.py +2726 -0
- package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/channels/test_telegram_channel.py +552 -0
- package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/logging/test_logging.py +132 -0
- package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/prompts/test_prompts.py +569 -0
- package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/test_anthropic_provider.py +185 -0
- package/backend/tests/unit/providers/test_errors.py +68 -0
- package/backend/tests/unit/providers/test_extract_delta_parts.py +22 -0
- package/backend/tests/unit/providers/test_openai_provider.py +139 -0
- package/backend/tests/unit/providers/test_openai_responses.py +402 -0
- package/backend/tests/unit/providers/test_provider_gateway.py +359 -0
- package/backend/tests/unit/providers/test_think_tag_parser.py +36 -0
- package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/test_prompts_routes.py +104 -0
- package/backend/tests/unit/routes/test_providers_route.py +368 -0
- package/backend/tests/unit/routes/test_roles_routes.py +426 -0
- package/backend/tests/unit/routes/test_settings_routes.py +1138 -0
- package/backend/tests/unit/routes/test_stats_routes.py +149 -0
- package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/runtime/test_bootstrap_runtime.py +1012 -0
- package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/sandbox/test_sandbox_tools.py +78 -0
- package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/security/test_security.py +110 -0
- package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/settings/test_settings_roles.py +711 -0
- package/backend/tests/unit/test_access.py +45 -0
- package/backend/tests/unit/test_cli.py +124 -0
- package/backend/tests/unit/test_graph_runtime.py +72 -0
- package/backend/tests/unit/test_network.py +51 -0
- package/backend/tests/unit/test_state_sqlite_storage.py +93 -0
- package/backend/tests/unit/test_workspace_store.py +231 -0
- package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/test_connect_tool.py +229 -0
- package/backend/tests/unit/tools/test_create_agent_tool.py +524 -0
- package/backend/tests/unit/tools/test_delete_tab_tool.py +83 -0
- package/backend/tests/unit/tools/test_edit_tool.py +115 -0
- package/backend/tests/unit/tools/test_exec_tool.py +81 -0
- package/backend/tests/unit/tools/test_fetch_tool.py +65 -0
- package/backend/tests/unit/tools/test_manage_prompts_tool.py +117 -0
- package/backend/tests/unit/tools/test_manage_providers_tool.py +458 -0
- package/backend/tests/unit/tools/test_manage_roles_tool.py +411 -0
- package/backend/tests/unit/tools/test_manage_settings_tool.py +608 -0
- package/backend/tests/unit/tools/test_read_tool.py +33 -0
- package/backend/tests/unit/tools/test_set_permissions_tool.py +391 -0
- package/backend/tests/unit/tools/test_todo_tool.py +37 -0
- package/backend/tests/unit/tools/test_tool_registry.py +91 -0
- package/backend/uv.lock +1144 -0
- package/bin/flowent.mjs +62 -36
- package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +1 -0
- package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +1 -0
- package/dist/frontend/assets/HomePage-C0hAx9_l.js +3 -0
- package/dist/frontend/assets/McpPage-DkrYLvBv.js +7 -0
- package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +1 -0
- package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +1 -0
- package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +3 -0
- package/dist/frontend/assets/RolesPage-CqcclGRw.js +1 -0
- package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +3 -0
- package/dist/frontend/assets/StatsPage-BX9khYzu.js +1 -0
- package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +1 -0
- package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
- package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
- package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +1 -0
- package/dist/frontend/assets/badge-74-3jsCg.js +1 -0
- package/dist/frontend/assets/constants-XUzFf6i1.js +1 -0
- package/dist/frontend/assets/datetime-m6_O_Ci9.js +1 -0
- package/dist/frontend/assets/dialog-BeGSweF6.js +1 -0
- package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- package/dist/frontend/assets/graph-vendor-CHpVij2M.css +1 -0
- package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +7 -0
- package/dist/frontend/assets/index-BHC1Vhy8.css +1 -0
- package/dist/frontend/assets/index-CL1ALZ3r.js +10 -0
- package/dist/frontend/assets/layout.worker-jMHqAFbP.js +24 -0
- package/dist/frontend/assets/markdown-vendor-DVdy_w12.js +29 -0
- package/dist/frontend/assets/modelParams-CaHd0903.js +1 -0
- package/dist/frontend/assets/react-vendor-mEs_JJxa.js +9 -0
- package/dist/frontend/assets/roles-2OLDeTc5.js +1 -0
- package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +1 -0
- package/dist/frontend/assets/select-DL_LPeDj.js +1 -0
- package/dist/frontend/assets/shared-CMxbpLeQ.js +1 -0
- package/dist/frontend/assets/triState-DEr3NkXV.js +1 -0
- package/dist/frontend/assets/ui-vendor-Dg9NNnWX.js +51 -0
- package/dist/frontend/index.html +36 -0
- package/package.json +27 -41
- package/dist/.next/BUILD_ID +0 -1
- package/dist/.next/app-path-routes-manifest.json +0 -6
- package/dist/.next/build-manifest.json +0 -20
- package/dist/.next/package.json +0 -1
- package/dist/.next/prerender-manifest.json +0 -114
- package/dist/.next/required-server-files.json +0 -333
- package/dist/.next/routes-manifest.json +0 -69
- package/dist/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/_global-error/page/build-manifest.json +0 -16
- package/dist/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
- package/dist/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
- package/dist/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
- package/dist/.next/server/app/_global-error/page.js +0 -9
- package/dist/.next/server/app/_global-error/page.js.map +0 -5
- package/dist/.next/server/app/_global-error/page.js.nft.json +0 -1
- package/dist/.next/server/app/_global-error/page_client-reference-manifest.js +0 -3
- package/dist/.next/server/app/_global-error.html +0 -1
- package/dist/.next/server/app/_global-error.meta +0 -15
- package/dist/.next/server/app/_global-error.rsc +0 -14
- package/dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
- package/dist/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
- package/dist/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
- package/dist/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
- package/dist/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
- package/dist/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/_not-found/page/build-manifest.json +0 -16
- package/dist/.next/server/app/_not-found/page/next-font-manifest.json +0 -10
- package/dist/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
- package/dist/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
- package/dist/.next/server/app/_not-found/page.js +0 -13
- package/dist/.next/server/app/_not-found/page.js.map +0 -5
- package/dist/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +0 -3
- package/dist/.next/server/app/_not-found.html +0 -1
- package/dist/.next/server/app/_not-found.meta +0 -16
- package/dist/.next/server/app/_not-found.rsc +0 -16
- package/dist/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
- package/dist/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
- package/dist/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
- package/dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
- package/dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
- package/dist/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
- package/dist/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/icon.svg/route/build-manifest.json +0 -9
- package/dist/.next/server/app/icon.svg/route.js +0 -6
- package/dist/.next/server/app/icon.svg/route.js.map +0 -5
- package/dist/.next/server/app/icon.svg/route.js.nft.json +0 -1
- package/dist/.next/server/app/icon.svg.meta +0 -1
- package/dist/.next/server/app/index.html +0 -1
- package/dist/.next/server/app/index.meta +0 -14
- package/dist/.next/server/app/index.rsc +0 -15
- package/dist/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -5
- package/dist/.next/server/app/index.segments/_full.segment.rsc +0 -15
- package/dist/.next/server/app/index.segments/_head.segment.rsc +0 -6
- package/dist/.next/server/app/index.segments/_index.segment.rsc +0 -5
- package/dist/.next/server/app/index.segments/_tree.segment.rsc +0 -3
- package/dist/.next/server/app/page/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/page/build-manifest.json +0 -16
- package/dist/.next/server/app/page/next-font-manifest.json +0 -10
- package/dist/.next/server/app/page/react-loadable-manifest.json +0 -1
- package/dist/.next/server/app/page/server-reference-manifest.json +0 -4
- package/dist/.next/server/app/page.js +0 -14
- package/dist/.next/server/app/page.js.map +0 -5
- package/dist/.next/server/app/page.js.nft.json +0 -1
- package/dist/.next/server/app/page_client-reference-manifest.js +0 -3
- package/dist/.next/server/app-paths-manifest.json +0 -6
- package/dist/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +0 -3
- package/dist/.next/server/chunks/[root-of-the-server]__0vcj1q1._.js +0 -13
- package/dist/.next/server/chunks/[turbopack]_runtime.js +0 -903
- package/dist/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_0ihu0u9._.js +0 -6
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_12u3mib._.js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_forbidden_04fbe_..js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_global-error_0brpl_..js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_unauthorized_0~2g66g.js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_0~cyr1_.js +0 -4
- package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_1105emf.js +0 -4
- package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_11uhyqv.js +0 -4
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0.t9_75._.js +0 -33
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0c0ud_z._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0f9_8d4._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0l5ko41._.js +0 -19
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0mn6z7i._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0npxxst._.js +0 -33
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0qjhaca._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0rwgw3s._.js +0 -3
- package/dist/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -903
- package/dist/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
- package/dist/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
- package/dist/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
- package/dist/.next/server/chunks/ssr/node_modules__pnpm_056~6.6._.js +0 -3
- package/dist/.next/server/chunks/ssr/node_modules__pnpm_0~j0k.e._.js +0 -33
- package/dist/.next/server/functions-config-manifest.json +0 -4
- package/dist/.next/server/middleware-build-manifest.js +0 -20
- package/dist/.next/server/middleware-manifest.json +0 -6
- package/dist/.next/server/next-font-manifest.js +0 -1
- package/dist/.next/server/next-font-manifest.json +0 -13
- package/dist/.next/server/pages/404.html +0 -1
- package/dist/.next/server/pages/500.html +0 -1
- package/dist/.next/server/pages-manifest.json +0 -4
- package/dist/.next/server/prefetch-hints.json +0 -1
- package/dist/.next/server/server-reference-manifest.js +0 -1
- package/dist/.next/server/server-reference-manifest.json +0 -5
- package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_buildManifest.js +0 -11
- package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_clientMiddlewareManifest.js +0 -1
- package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_ssgManifest.js +0 -1
- package/dist/.next/static/chunks/01qk2~bgf76vu.js +0 -1
- package/dist/.next/static/chunks/03~yq9q893hmn.js +0 -1
- package/dist/.next/static/chunks/080queev.r2uy.js +0 -31
- package/dist/.next/static/chunks/0v3lyuj75aq50.js +0 -1
- package/dist/.next/static/chunks/10b~xdx5c-i7s.js +0 -5
- package/dist/.next/static/chunks/14gla2ascffgv.css +0 -2
- package/dist/.next/static/chunks/turbopack-0m-970~qvs7sc.js +0 -1
- package/dist/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
- package/dist/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
- package/dist/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
- package/dist/package.json +0 -88
- package/dist/server.js +0 -38
- /package/{dist/.next/server/app/icon.svg.body → backend/src/flowent/static/favicon.svg} +0 -0
- /package/dist/{.next/static/media/icon.0.r~afrtrocz9.svg → frontend/favicon.svg} +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, HTTPException
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from flowent.assistant_commands import (
|
|
10
|
+
AssistantCommandError,
|
|
11
|
+
execute_assistant_command_input,
|
|
12
|
+
)
|
|
13
|
+
from flowent.image_assets import require_image_asset
|
|
14
|
+
from flowent.models import (
|
|
15
|
+
Message,
|
|
16
|
+
content_parts_to_text,
|
|
17
|
+
has_image_parts,
|
|
18
|
+
parse_content_parts_payload,
|
|
19
|
+
)
|
|
20
|
+
from flowent.models import TextPart as ModelTextPart
|
|
21
|
+
from flowent.providers.errors import LLMProviderError
|
|
22
|
+
from flowent.registry import registry
|
|
23
|
+
|
|
24
|
+
router = APIRouter()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_assistant():
|
|
28
|
+
return registry.get_assistant()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.get("/api/assistant")
|
|
32
|
+
async def get_assistant() -> dict:
|
|
33
|
+
assistant = _get_assistant()
|
|
34
|
+
if assistant is None:
|
|
35
|
+
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
36
|
+
return {
|
|
37
|
+
"id": assistant.uuid,
|
|
38
|
+
"name": assistant.config.name,
|
|
39
|
+
"role_name": assistant.config.role_name,
|
|
40
|
+
"state": assistant.state.value,
|
|
41
|
+
"connections": assistant.get_connections_snapshot(),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AssistantMessageRequest(BaseModel):
|
|
46
|
+
content: str | None = None
|
|
47
|
+
parts: list[AssistantMessagePart] | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AssistantMessagePart(BaseModel):
|
|
51
|
+
type: Literal["text", "image"]
|
|
52
|
+
text: str | None = None
|
|
53
|
+
asset_id: str | None = None
|
|
54
|
+
mime_type: str | None = None
|
|
55
|
+
width: int | None = None
|
|
56
|
+
height: int | None = None
|
|
57
|
+
alt: str | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
AssistantMessageRequest.model_rebuild()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AssistantRetryResponse(BaseModel):
|
|
64
|
+
status: Literal["retried"]
|
|
65
|
+
message_id: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _parse_request_parts(req: AssistantMessageRequest):
|
|
69
|
+
if req.parts:
|
|
70
|
+
parts = parse_content_parts_payload(
|
|
71
|
+
[part.model_dump(exclude_none=True) for part in req.parts]
|
|
72
|
+
)
|
|
73
|
+
elif isinstance(req.content, str):
|
|
74
|
+
parts = [ModelTextPart(text=req.content)]
|
|
75
|
+
else:
|
|
76
|
+
parts = []
|
|
77
|
+
if not parts:
|
|
78
|
+
raise HTTPException(status_code=400, detail="Assistant message cannot be empty")
|
|
79
|
+
if not has_image_parts(parts) and not content_parts_to_text(parts).strip():
|
|
80
|
+
raise HTTPException(status_code=400, detail="Assistant message cannot be empty")
|
|
81
|
+
for part in parts:
|
|
82
|
+
asset_id = getattr(part, "asset_id", None)
|
|
83
|
+
if isinstance(asset_id, str):
|
|
84
|
+
require_image_asset(asset_id)
|
|
85
|
+
return parts
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@router.post("/api/assistant/message")
|
|
89
|
+
async def send_assistant_message(req: AssistantMessageRequest) -> dict:
|
|
90
|
+
assistant = _get_assistant()
|
|
91
|
+
if assistant is None:
|
|
92
|
+
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
parts = _parse_request_parts(req)
|
|
96
|
+
except ValueError as exc:
|
|
97
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
98
|
+
|
|
99
|
+
if has_image_parts(parts) and not assistant.supports_input_image():
|
|
100
|
+
raise HTTPException(
|
|
101
|
+
status_code=409,
|
|
102
|
+
detail="Assistant current model does not support `input_image`.",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
command_input = (
|
|
106
|
+
parts[0].text
|
|
107
|
+
if len(parts) == 1 and isinstance(parts[0], ModelTextPart)
|
|
108
|
+
else None
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
executed_command = (
|
|
113
|
+
execute_assistant_command_input(assistant, command_input)
|
|
114
|
+
if isinstance(command_input, str)
|
|
115
|
+
else None
|
|
116
|
+
)
|
|
117
|
+
except AssistantCommandError as exc:
|
|
118
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
119
|
+
except (RuntimeError, TimeoutError, LLMProviderError) as exc:
|
|
120
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
121
|
+
|
|
122
|
+
if executed_command is not None:
|
|
123
|
+
return {
|
|
124
|
+
"status": "command_executed",
|
|
125
|
+
"command_name": executed_command.command_name,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
message_id = str(uuid.uuid4())
|
|
129
|
+
msg = Message(
|
|
130
|
+
from_id="human",
|
|
131
|
+
to_id=assistant.uuid,
|
|
132
|
+
parts=parts,
|
|
133
|
+
message_id=message_id,
|
|
134
|
+
)
|
|
135
|
+
assistant.enqueue_message(msg)
|
|
136
|
+
return {"status": "sent", "message_id": message_id}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.post(
|
|
140
|
+
"/api/assistant/messages/{message_id}/retry",
|
|
141
|
+
response_model=AssistantRetryResponse,
|
|
142
|
+
)
|
|
143
|
+
async def retry_assistant_message(message_id: str) -> AssistantRetryResponse:
|
|
144
|
+
assistant = _get_assistant()
|
|
145
|
+
if assistant is None:
|
|
146
|
+
raise HTTPException(status_code=404, detail="Assistant not found")
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
retried_message_id = assistant.retry_human_message(message_id=message_id)
|
|
150
|
+
except LookupError as exc:
|
|
151
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
152
|
+
except (RuntimeError, TimeoutError, ValueError) as exc:
|
|
153
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
154
|
+
|
|
155
|
+
return AssistantRetryResponse(status="retried", message_id=retried_message_id)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, File, HTTPException, UploadFile
|
|
4
|
+
from fastapi.responses import FileResponse
|
|
5
|
+
|
|
6
|
+
from flowent.image_assets import create_image_asset, get_image_asset
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
image_upload_file = File(...)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.post("/api/image-assets")
|
|
13
|
+
async def upload_image_asset(
|
|
14
|
+
file: UploadFile = image_upload_file,
|
|
15
|
+
) -> dict[str, object]:
|
|
16
|
+
try:
|
|
17
|
+
data = await file.read()
|
|
18
|
+
asset = create_image_asset(
|
|
19
|
+
data,
|
|
20
|
+
mime_type=file.content_type,
|
|
21
|
+
original_name=file.filename,
|
|
22
|
+
)
|
|
23
|
+
except ValueError as exc:
|
|
24
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
25
|
+
return asset.serialize()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@router.get("/api/image-assets/{asset_id}")
|
|
29
|
+
async def get_uploaded_image_asset(asset_id: str) -> FileResponse:
|
|
30
|
+
asset = get_image_asset(asset_id)
|
|
31
|
+
if asset is None:
|
|
32
|
+
raise HTTPException(status_code=404, detail="Image asset not found")
|
|
33
|
+
return FileResponse(asset.file_path, media_type=asset.mime_type)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
from flowent.mcp_service import MCPError, mcp_service
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MCPServerMutationRequest(BaseModel):
|
|
12
|
+
name: str
|
|
13
|
+
transport: str
|
|
14
|
+
enabled: bool = True
|
|
15
|
+
required: bool = False
|
|
16
|
+
startup_timeout_sec: int = 10
|
|
17
|
+
tool_timeout_sec: int = 30
|
|
18
|
+
enabled_tools: list[str] = []
|
|
19
|
+
disabled_tools: list[str] = []
|
|
20
|
+
scopes: list[str] = []
|
|
21
|
+
oauth_resource: str = ""
|
|
22
|
+
launcher: str = ""
|
|
23
|
+
command: str = ""
|
|
24
|
+
args: list[str] = []
|
|
25
|
+
env: dict[str, str] = {}
|
|
26
|
+
env_vars: list[str] = []
|
|
27
|
+
cwd: str = ""
|
|
28
|
+
url: str = ""
|
|
29
|
+
bearer_token_env_var: str = ""
|
|
30
|
+
http_headers: dict[str, str] = {}
|
|
31
|
+
env_http_headers: list[str] = []
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MCPPromptPreviewRequest(BaseModel):
|
|
35
|
+
name: str
|
|
36
|
+
arguments: dict[str, object] = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@router.get("/api/mcp")
|
|
40
|
+
async def get_mcp_state() -> dict[str, object]:
|
|
41
|
+
return {"servers": mcp_service.list_server_payloads()}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@router.post("/api/mcp/refresh")
|
|
45
|
+
async def refresh_all_mcp_servers() -> dict[str, object]:
|
|
46
|
+
return {"servers": mcp_service.refresh_all()}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post("/api/mcp/servers")
|
|
50
|
+
async def create_mcp_server(req: MCPServerMutationRequest) -> dict[str, object]:
|
|
51
|
+
try:
|
|
52
|
+
snapshot = mcp_service.create_or_update_server(
|
|
53
|
+
current_name=None,
|
|
54
|
+
config_data=req.model_dump(),
|
|
55
|
+
)
|
|
56
|
+
except MCPError as exc:
|
|
57
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
58
|
+
return {"snapshot": snapshot}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@router.patch("/api/mcp/servers/{server_name}")
|
|
62
|
+
async def update_mcp_server(
|
|
63
|
+
server_name: str,
|
|
64
|
+
req: MCPServerMutationRequest,
|
|
65
|
+
) -> dict[str, object]:
|
|
66
|
+
try:
|
|
67
|
+
snapshot = mcp_service.create_or_update_server(
|
|
68
|
+
current_name=server_name,
|
|
69
|
+
config_data=req.model_dump(),
|
|
70
|
+
)
|
|
71
|
+
except MCPError as exc:
|
|
72
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
73
|
+
return {"snapshot": snapshot}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.delete("/api/mcp/servers/{server_name}")
|
|
77
|
+
async def delete_mcp_server(server_name: str) -> dict[str, object]:
|
|
78
|
+
try:
|
|
79
|
+
mcp_service.delete_server(server_name)
|
|
80
|
+
except MCPError as exc:
|
|
81
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
82
|
+
return {"status": "deleted", "server_name": server_name}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@router.post("/api/mcp/servers/{server_name}/refresh")
|
|
86
|
+
async def refresh_mcp_server(server_name: str) -> dict[str, object]:
|
|
87
|
+
try:
|
|
88
|
+
snapshot = mcp_service.refresh_server(server_name)
|
|
89
|
+
except MCPError as exc:
|
|
90
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
91
|
+
return {"snapshot": snapshot}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.post("/api/mcp/servers/{server_name}/login")
|
|
95
|
+
async def login_mcp_server(server_name: str) -> dict[str, object]:
|
|
96
|
+
try:
|
|
97
|
+
snapshot = mcp_service.login_server(server_name)
|
|
98
|
+
except MCPError as exc:
|
|
99
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
100
|
+
return {"snapshot": snapshot}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@router.post("/api/mcp/servers/{server_name}/logout")
|
|
104
|
+
async def logout_mcp_server(server_name: str) -> dict[str, object]:
|
|
105
|
+
try:
|
|
106
|
+
snapshot = mcp_service.logout_server(server_name)
|
|
107
|
+
except MCPError as exc:
|
|
108
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
109
|
+
return {"snapshot": snapshot}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@router.post("/api/mcp/servers/{server_name}/prompt-preview")
|
|
113
|
+
async def preview_mcp_prompt(
|
|
114
|
+
server_name: str,
|
|
115
|
+
req: MCPPromptPreviewRequest,
|
|
116
|
+
) -> dict[str, object]:
|
|
117
|
+
try:
|
|
118
|
+
preview = mcp_service.preview_server_prompt(
|
|
119
|
+
server_name=server_name,
|
|
120
|
+
name=req.name,
|
|
121
|
+
arguments=req.arguments,
|
|
122
|
+
)
|
|
123
|
+
except MCPError as exc:
|
|
124
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
125
|
+
return {"preview": preview}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
router = APIRouter()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@router.get("/health")
|
|
9
|
+
async def health_check() -> dict:
|
|
10
|
+
return {"status": "healthy"}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@router.get("/api/meta")
|
|
14
|
+
async def get_meta() -> dict:
|
|
15
|
+
from flowent._version import __version__
|
|
16
|
+
from flowent.providers.registry import ProviderType
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
"provider_types": [pt.value for pt in ProviderType],
|
|
20
|
+
"version": __version__,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get("/api/tools")
|
|
25
|
+
async def list_tools() -> dict:
|
|
26
|
+
from flowent.tools import list_agent_visible_tool_descriptors
|
|
27
|
+
|
|
28
|
+
return {"tools": list_agent_visible_tool_descriptors()}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, HTTPException
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from flowent.graph_service import is_tab_leader, list_node_connection_ids
|
|
9
|
+
from flowent.registry import registry
|
|
10
|
+
from flowent.settings import (
|
|
11
|
+
find_provider,
|
|
12
|
+
find_role,
|
|
13
|
+
get_settings,
|
|
14
|
+
resolve_model_info,
|
|
15
|
+
)
|
|
16
|
+
from flowent.tools import MINIMUM_TOOLS
|
|
17
|
+
from flowent.workspace_store import workspace_store
|
|
18
|
+
|
|
19
|
+
router = APIRouter()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class DispatchNodeMessagePart(BaseModel):
|
|
23
|
+
type: Literal["text", "image"]
|
|
24
|
+
text: str | None = None
|
|
25
|
+
asset_id: str | None = None
|
|
26
|
+
mime_type: str | None = None
|
|
27
|
+
width: int | None = None
|
|
28
|
+
height: int | None = None
|
|
29
|
+
alt: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DispatchNodeMessageRequest(BaseModel):
|
|
33
|
+
content: str | None = None
|
|
34
|
+
parts: list[DispatchNodeMessagePart] | None = None
|
|
35
|
+
from_id: str = "human"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
DispatchNodeMessageRequest.model_rebuild()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _parse_dispatch_parts(req: DispatchNodeMessageRequest):
|
|
42
|
+
from flowent.image_assets import require_image_asset
|
|
43
|
+
from flowent.models import TextPart as ModelTextPart
|
|
44
|
+
from flowent.models import (
|
|
45
|
+
content_parts_to_text,
|
|
46
|
+
has_image_parts,
|
|
47
|
+
parse_content_parts_payload,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if req.parts:
|
|
51
|
+
parts = parse_content_parts_payload(
|
|
52
|
+
[part.model_dump(exclude_none=True) for part in req.parts]
|
|
53
|
+
)
|
|
54
|
+
elif isinstance(req.content, str):
|
|
55
|
+
parts = [ModelTextPart(text=req.content)]
|
|
56
|
+
else:
|
|
57
|
+
parts = []
|
|
58
|
+
|
|
59
|
+
if not parts:
|
|
60
|
+
raise HTTPException(status_code=400, detail="Node message cannot be empty")
|
|
61
|
+
if not has_image_parts(parts) and not content_parts_to_text(parts).strip():
|
|
62
|
+
raise HTTPException(status_code=400, detail="Node message cannot be empty")
|
|
63
|
+
|
|
64
|
+
for part in parts:
|
|
65
|
+
asset_id = getattr(part, "asset_id", None)
|
|
66
|
+
if isinstance(asset_id, str):
|
|
67
|
+
require_image_asset(asset_id)
|
|
68
|
+
return parts
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _serialize_model_capabilities(role_name: str | None) -> dict[str, bool] | None:
|
|
72
|
+
settings = get_settings()
|
|
73
|
+
provider_id = settings.model.active_provider_id
|
|
74
|
+
model_id = settings.model.active_model
|
|
75
|
+
use_system_model_overrides = True
|
|
76
|
+
role_cfg = find_role(settings, role_name) if role_name else None
|
|
77
|
+
if (
|
|
78
|
+
role_cfg is not None
|
|
79
|
+
and role_cfg.model is not None
|
|
80
|
+
and role_cfg.model.provider_id
|
|
81
|
+
and role_cfg.model.model
|
|
82
|
+
):
|
|
83
|
+
provider_id = role_cfg.model.provider_id
|
|
84
|
+
model_id = role_cfg.model.model
|
|
85
|
+
use_system_model_overrides = False
|
|
86
|
+
if not provider_id or not model_id:
|
|
87
|
+
return None
|
|
88
|
+
provider = find_provider(settings, provider_id)
|
|
89
|
+
if provider is None:
|
|
90
|
+
return None
|
|
91
|
+
model_info = resolve_model_info(
|
|
92
|
+
provider=provider,
|
|
93
|
+
model_id=model_id,
|
|
94
|
+
input_image=settings.model.input_image if use_system_model_overrides else None,
|
|
95
|
+
output_image=(
|
|
96
|
+
settings.model.output_image if use_system_model_overrides else None
|
|
97
|
+
),
|
|
98
|
+
context_window_tokens=(
|
|
99
|
+
settings.model.context_window_tokens if use_system_model_overrides else None
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
return {
|
|
103
|
+
"input_image": model_info.capabilities.input_image,
|
|
104
|
+
"output_image": model_info.capabilities.output_image,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@router.get("/api/nodes")
|
|
109
|
+
async def list_nodes() -> dict:
|
|
110
|
+
nodes_by_id: dict[str, dict[str, object]] = {}
|
|
111
|
+
|
|
112
|
+
assistant = registry.get_assistant()
|
|
113
|
+
if assistant is not None:
|
|
114
|
+
nodes_by_id[assistant.uuid] = {
|
|
115
|
+
"id": assistant.uuid,
|
|
116
|
+
"node_type": assistant.config.node_type.value,
|
|
117
|
+
"workflow_id": assistant.config.tab_id,
|
|
118
|
+
"role_name": assistant.config.role_name,
|
|
119
|
+
"state": assistant.state.value,
|
|
120
|
+
"connections": assistant.get_connections_snapshot(),
|
|
121
|
+
"name": assistant.config.name,
|
|
122
|
+
"is_leader": False,
|
|
123
|
+
"todos": [t.serialize() for t in assistant.todos],
|
|
124
|
+
"capabilities": _serialize_model_capabilities(assistant.config.role_name),
|
|
125
|
+
"position": None,
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for record in workspace_store.list_node_records():
|
|
129
|
+
live = registry.get(record.id)
|
|
130
|
+
nodes_by_id[record.id] = {
|
|
131
|
+
"id": record.id,
|
|
132
|
+
"node_type": record.config.node_type.value,
|
|
133
|
+
"workflow_id": record.config.tab_id,
|
|
134
|
+
"role_name": record.config.role_name,
|
|
135
|
+
"is_leader": is_tab_leader(node_id=record.id, tab_id=record.config.tab_id),
|
|
136
|
+
"state": (live.state if live is not None else record.state).value,
|
|
137
|
+
"connections": (
|
|
138
|
+
list_node_connection_ids(
|
|
139
|
+
tab_id=record.config.tab_id,
|
|
140
|
+
node_id=record.id,
|
|
141
|
+
)
|
|
142
|
+
if record.config.tab_id
|
|
143
|
+
else []
|
|
144
|
+
),
|
|
145
|
+
"name": record.config.name,
|
|
146
|
+
"todos": [
|
|
147
|
+
todo.serialize()
|
|
148
|
+
for todo in (
|
|
149
|
+
live.get_todos_snapshot() if live is not None else record.todos
|
|
150
|
+
)
|
|
151
|
+
],
|
|
152
|
+
"capabilities": _serialize_model_capabilities(record.config.role_name),
|
|
153
|
+
"position": record.position.serialize()
|
|
154
|
+
if record.position is not None
|
|
155
|
+
else None,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for node in registry.get_all():
|
|
159
|
+
if node.uuid in nodes_by_id:
|
|
160
|
+
continue
|
|
161
|
+
nodes_by_id[node.uuid] = {
|
|
162
|
+
"id": node.uuid,
|
|
163
|
+
"node_type": node.config.node_type.value,
|
|
164
|
+
"workflow_id": node.config.tab_id,
|
|
165
|
+
"role_name": node.config.role_name,
|
|
166
|
+
"is_leader": is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id),
|
|
167
|
+
"state": node.state.value,
|
|
168
|
+
"connections": (
|
|
169
|
+
list_node_connection_ids(
|
|
170
|
+
tab_id=node.config.tab_id,
|
|
171
|
+
node_id=node.uuid,
|
|
172
|
+
)
|
|
173
|
+
if node.config.tab_id
|
|
174
|
+
else node.get_connections_snapshot()
|
|
175
|
+
),
|
|
176
|
+
"name": node.config.name,
|
|
177
|
+
"todos": [t.serialize() for t in node.todos],
|
|
178
|
+
"capabilities": _serialize_model_capabilities(node.config.role_name),
|
|
179
|
+
"position": None,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
"nodes": list(nodes_by_id.values()),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@router.get("/api/nodes/{node_id}")
|
|
188
|
+
async def get_node(node_id: str) -> dict:
|
|
189
|
+
node = registry.get(node_id)
|
|
190
|
+
record = workspace_store.get_node_record(node_id)
|
|
191
|
+
|
|
192
|
+
if node is None and record is None:
|
|
193
|
+
raise HTTPException(status_code=404, detail="Node not found")
|
|
194
|
+
|
|
195
|
+
if node is not None:
|
|
196
|
+
record_id = node.uuid
|
|
197
|
+
record_state = node.state
|
|
198
|
+
target_config = node.config
|
|
199
|
+
else:
|
|
200
|
+
assert record is not None
|
|
201
|
+
record_id = record.id
|
|
202
|
+
record_state = record.state
|
|
203
|
+
target_config = record.config
|
|
204
|
+
history = (
|
|
205
|
+
node.get_history_snapshot()
|
|
206
|
+
if node is not None
|
|
207
|
+
else (record.history if record is not None else [])
|
|
208
|
+
)
|
|
209
|
+
todos = (
|
|
210
|
+
node.get_todos_snapshot()
|
|
211
|
+
if node is not None
|
|
212
|
+
else (record.todos if record is not None else [])
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
"id": record_id,
|
|
217
|
+
"node_type": target_config.node_type.value,
|
|
218
|
+
"workflow_id": target_config.tab_id,
|
|
219
|
+
"role_name": target_config.role_name,
|
|
220
|
+
"is_leader": is_tab_leader(node_id=record_id, tab_id=target_config.tab_id),
|
|
221
|
+
"state": record_state.value,
|
|
222
|
+
"contacts": node.get_contact_ids_snapshot() if node is not None else [],
|
|
223
|
+
"connections": (
|
|
224
|
+
list_node_connection_ids(
|
|
225
|
+
tab_id=target_config.tab_id,
|
|
226
|
+
node_id=record_id,
|
|
227
|
+
)
|
|
228
|
+
if target_config.tab_id
|
|
229
|
+
else (node.get_connections_snapshot() if node is not None else [])
|
|
230
|
+
),
|
|
231
|
+
"name": target_config.name,
|
|
232
|
+
"todos": [t.serialize() for t in todos],
|
|
233
|
+
"capabilities": _serialize_model_capabilities(target_config.role_name),
|
|
234
|
+
"tools": sorted(set(target_config.tools) | set(MINIMUM_TOOLS)),
|
|
235
|
+
"write_dirs": list(target_config.write_dirs),
|
|
236
|
+
"allow_network": target_config.allow_network,
|
|
237
|
+
"position": record.position.serialize()
|
|
238
|
+
if record is not None and record.position is not None
|
|
239
|
+
else None,
|
|
240
|
+
"history": [entry.serialize() for entry in history],
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@router.post("/api/nodes/{node_id}/terminate")
|
|
245
|
+
async def terminate_node(node_id: str) -> dict:
|
|
246
|
+
node = registry.get(node_id)
|
|
247
|
+
if node is None:
|
|
248
|
+
raise HTTPException(status_code=404, detail="Node not found")
|
|
249
|
+
|
|
250
|
+
from flowent.models import NodeType
|
|
251
|
+
|
|
252
|
+
if node.config.node_type == NodeType.ASSISTANT:
|
|
253
|
+
raise HTTPException(status_code=400, detail="Cannot terminate assistant")
|
|
254
|
+
if is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id):
|
|
255
|
+
raise HTTPException(
|
|
256
|
+
status_code=400,
|
|
257
|
+
detail="Cannot terminate a workflow Leader directly",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
node.request_termination("user_requested")
|
|
261
|
+
return {"status": "terminating"}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@router.post("/api/nodes/{node_id}/interrupt")
|
|
265
|
+
async def interrupt_node(node_id: str) -> dict:
|
|
266
|
+
node = registry.get(node_id)
|
|
267
|
+
if node is None:
|
|
268
|
+
raise HTTPException(status_code=404, detail="Node not found")
|
|
269
|
+
if not node.request_interrupt():
|
|
270
|
+
return {"status": "ignored"}
|
|
271
|
+
return {"status": "interrupting"}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@router.post("/api/nodes/{node_id}/messages/{message_id}/retry")
|
|
275
|
+
async def retry_node_message(node_id: str, message_id: str) -> dict:
|
|
276
|
+
node = registry.get(node_id)
|
|
277
|
+
if node is None:
|
|
278
|
+
raise HTTPException(status_code=404, detail="Node not found")
|
|
279
|
+
from flowent.models import NodeType
|
|
280
|
+
|
|
281
|
+
if node.config.node_type == NodeType.ASSISTANT:
|
|
282
|
+
raise HTTPException(
|
|
283
|
+
status_code=400,
|
|
284
|
+
detail="Use /api/assistant/messages/{message_id}/retry for Assistant retry",
|
|
285
|
+
)
|
|
286
|
+
if not is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id):
|
|
287
|
+
raise HTTPException(
|
|
288
|
+
status_code=400,
|
|
289
|
+
detail="Only a Workflow Leader can retry chat history",
|
|
290
|
+
)
|
|
291
|
+
try:
|
|
292
|
+
retried_message_id = node.retry_received_message(message_id=message_id)
|
|
293
|
+
except LookupError as exc:
|
|
294
|
+
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
295
|
+
except (RuntimeError, TimeoutError, ValueError) as exc:
|
|
296
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
297
|
+
return {"status": "retried", "message_id": retried_message_id}
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@router.post("/api/nodes/{node_id}/clear-chat")
|
|
301
|
+
async def clear_node_chat(node_id: str) -> dict:
|
|
302
|
+
from flowent.models import NodeType
|
|
303
|
+
|
|
304
|
+
node = registry.get(node_id)
|
|
305
|
+
if node is None:
|
|
306
|
+
raise HTTPException(status_code=404, detail="Node not found")
|
|
307
|
+
if node.config.node_type != NodeType.ASSISTANT:
|
|
308
|
+
raise HTTPException(status_code=400, detail="Can only clear assistant chat")
|
|
309
|
+
|
|
310
|
+
try:
|
|
311
|
+
node.clear_chat_history()
|
|
312
|
+
except RuntimeError as exc:
|
|
313
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
314
|
+
except TimeoutError as exc:
|
|
315
|
+
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
|
316
|
+
|
|
317
|
+
return {"status": "cleared"}
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@router.post("/api/nodes/{node_id}/messages")
|
|
321
|
+
async def dispatch_node_message(node_id: str, req: DispatchNodeMessageRequest) -> dict:
|
|
322
|
+
from flowent.graph_service import dispatch_node_message
|
|
323
|
+
from flowent.models import NodeType, content_parts_to_text, has_image_parts
|
|
324
|
+
|
|
325
|
+
node = registry.get(node_id)
|
|
326
|
+
if node is None:
|
|
327
|
+
raise HTTPException(status_code=404, detail="Node not found")
|
|
328
|
+
|
|
329
|
+
if req.from_id != "human":
|
|
330
|
+
raise HTTPException(
|
|
331
|
+
status_code=400,
|
|
332
|
+
detail="Web UI node messages must originate from `human`",
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if node.config.node_type == NodeType.ASSISTANT:
|
|
336
|
+
raise HTTPException(
|
|
337
|
+
status_code=400,
|
|
338
|
+
detail="Use /api/assistant/message for Assistant input",
|
|
339
|
+
)
|
|
340
|
+
if not is_tab_leader(node_id=node.uuid, tab_id=node.config.tab_id):
|
|
341
|
+
raise HTTPException(
|
|
342
|
+
status_code=400,
|
|
343
|
+
detail="Human input can only target Assistant or a Workflow Leader",
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
try:
|
|
347
|
+
parts = _parse_dispatch_parts(req)
|
|
348
|
+
except ValueError as exc:
|
|
349
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
350
|
+
|
|
351
|
+
if has_image_parts(parts) and not node.supports_input_image():
|
|
352
|
+
raise HTTPException(
|
|
353
|
+
status_code=409,
|
|
354
|
+
detail="Current model does not support `input_image`.",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
error, message_id = dispatch_node_message(
|
|
358
|
+
node_id=node_id,
|
|
359
|
+
content=content_parts_to_text(parts),
|
|
360
|
+
parts=parts,
|
|
361
|
+
from_id="human",
|
|
362
|
+
)
|
|
363
|
+
if error is not None:
|
|
364
|
+
raise HTTPException(status_code=400, detail=error)
|
|
365
|
+
return {"status": "sent", "message_id": message_id}
|