flowent 0.0.7 → 0.0.11
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 +0 -3
- package/backend/README.md +0 -3
- package/backend/pyproject.toml +2 -8
- package/backend/src/flowent/__init__.py +6 -2
- package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/llm.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__/patch.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
- package/backend/src/flowent/agent.py +213 -3173
- package/backend/src/flowent/cli.py +19 -24
- package/backend/src/flowent/context.py +127 -0
- package/backend/src/flowent/llm.py +256 -0
- package/backend/src/flowent/logging.py +170 -129
- package/backend/src/flowent/main.py +321 -70
- package/backend/src/flowent/patch.py +182 -0
- package/backend/src/flowent/paths.py +11 -0
- package/backend/src/flowent/sandbox.py +214 -40
- package/backend/src/flowent/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/backend/src/flowent/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/backend/src/flowent/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/backend/src/flowent/static/assets/index-C76K95ty.js +81 -0
- package/backend/src/flowent/static/assets/index-iUMNKvlU.css +2 -0
- package/backend/src/flowent/static/flowent.png +0 -0
- package/backend/src/flowent/static/index.html +5 -25
- package/backend/src/flowent/storage.py +302 -0
- package/backend/src/flowent/tools.py +376 -0
- package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/test_agent_tools.py +477 -0
- package/backend/tests/test_health.py +12 -0
- package/backend/tests/test_llm_providers.py +113 -0
- package/backend/tests/test_logging.py +182 -0
- package/backend/tests/test_persistence.py +125 -0
- package/backend/tests/test_workspace_chat.py +578 -0
- package/backend/uv.lock +803 -99
- package/dist/frontend/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/dist/frontend/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/dist/frontend/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/dist/frontend/assets/index-C76K95ty.js +81 -0
- package/dist/frontend/assets/index-iUMNKvlU.css +2 -0
- package/dist/frontend/flowent.png +0 -0
- package/dist/frontend/index.html +5 -25
- package/package.json +1 -2
- 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__/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__/model_metadata.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/observability_service.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__/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__/workspace_store.cpython-313.pyc +0 -0
- package/backend/src/flowent/access.py +0 -247
- package/backend/src/flowent/assistant_commands.py +0 -115
- package/backend/src/flowent/channels/__init__.py +0 -3
- 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 +0 -615
- package/backend/src/flowent/config.py +0 -14
- package/backend/src/flowent/dev.py +0 -3
- package/backend/src/flowent/events.py +0 -157
- package/backend/src/flowent/graph_runtime.py +0 -60
- package/backend/src/flowent/graph_service.py +0 -2401
- package/backend/src/flowent/image_assets.py +0 -356
- package/backend/src/flowent/model_metadata.py +0 -102
- package/backend/src/flowent/models/__init__.py +0 -125
- 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 +0 -34
- package/backend/src/flowent/models/base.py +0 -24
- package/backend/src/flowent/models/blueprint.py +0 -176
- package/backend/src/flowent/models/content.py +0 -164
- package/backend/src/flowent/models/delta.py +0 -44
- package/backend/src/flowent/models/event.py +0 -51
- package/backend/src/flowent/models/graph.py +0 -472
- package/backend/src/flowent/models/history.py +0 -272
- package/backend/src/flowent/models/llm.py +0 -62
- package/backend/src/flowent/models/message.py +0 -33
- package/backend/src/flowent/models/tab.py +0 -85
- package/backend/src/flowent/models/todo.py +0 -10
- package/backend/src/flowent/network.py +0 -146
- package/backend/src/flowent/observability_service.py +0 -218
- package/backend/src/flowent/prompts/__init__.py +0 -67
- 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 +0 -250
- package/backend/src/flowent/prompts/steward.py +0 -64
- package/backend/src/flowent/providers/__init__.py +0 -23
- 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 +0 -468
- package/backend/src/flowent/providers/base_url.py +0 -60
- package/backend/src/flowent/providers/configuration.py +0 -189
- package/backend/src/flowent/providers/content.py +0 -122
- package/backend/src/flowent/providers/errors.py +0 -223
- package/backend/src/flowent/providers/gateway.py +0 -169
- package/backend/src/flowent/providers/gemini.py +0 -447
- package/backend/src/flowent/providers/headers.py +0 -20
- package/backend/src/flowent/providers/management.py +0 -96
- package/backend/src/flowent/providers/ollama.py +0 -293
- package/backend/src/flowent/providers/openai.py +0 -422
- package/backend/src/flowent/providers/openai_responses.py +0 -655
- package/backend/src/flowent/providers/registry.py +0 -144
- package/backend/src/flowent/providers/sse.py +0 -31
- package/backend/src/flowent/providers/thinking.py +0 -79
- package/backend/src/flowent/registry.py +0 -73
- package/backend/src/flowent/role_management.py +0 -270
- package/backend/src/flowent/routes/__init__.py +0 -26
- 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__/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__/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 +0 -48
- package/backend/src/flowent/routes/assistant.py +0 -158
- package/backend/src/flowent/routes/image_assets.py +0 -33
- package/backend/src/flowent/routes/meta.py +0 -28
- package/backend/src/flowent/routes/nodes.py +0 -423
- package/backend/src/flowent/routes/prompts.py +0 -46
- package/backend/src/flowent/routes/providers_route.py +0 -365
- package/backend/src/flowent/routes/roles.py +0 -207
- package/backend/src/flowent/routes/settings.py +0 -379
- package/backend/src/flowent/routes/tabs.py +0 -298
- package/backend/src/flowent/routes/ws.py +0 -33
- package/backend/src/flowent/runtime.py +0 -160
- package/backend/src/flowent/security.py +0 -37
- package/backend/src/flowent/settings.py +0 -2112
- package/backend/src/flowent/settings_management.py +0 -394
- package/backend/src/flowent/state_db.py +0 -108
- package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
- package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
- package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
- package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
- package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
- package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
- package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
- package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
- package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
- package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
- package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
- package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
- package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
- package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
- package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
- package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
- package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
- package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
- package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
- package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
- package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
- package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
- package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
- package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
- package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
- package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
- package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
- package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
- package/backend/src/flowent/static/favicon.svg +0 -4
- package/backend/src/flowent/tools/__init__.py +0 -176
- 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__/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 +0 -100
- package/backend/src/flowent/tools/contacts.py +0 -22
- package/backend/src/flowent/tools/create_agent.py +0 -191
- package/backend/src/flowent/tools/create_tab.py +0 -61
- package/backend/src/flowent/tools/delete_tab.py +0 -39
- package/backend/src/flowent/tools/edit.py +0 -142
- package/backend/src/flowent/tools/exec.py +0 -118
- package/backend/src/flowent/tools/fetch.py +0 -85
- package/backend/src/flowent/tools/idle.py +0 -27
- package/backend/src/flowent/tools/list_roles.py +0 -68
- package/backend/src/flowent/tools/list_tabs.py +0 -100
- package/backend/src/flowent/tools/list_tools.py +0 -28
- package/backend/src/flowent/tools/manage_prompts.py +0 -102
- package/backend/src/flowent/tools/manage_providers.py +0 -220
- package/backend/src/flowent/tools/manage_roles.py +0 -275
- package/backend/src/flowent/tools/manage_settings.py +0 -326
- package/backend/src/flowent/tools/read.py +0 -152
- package/backend/src/flowent/tools/send.py +0 -68
- package/backend/src/flowent/tools/set_permissions.py +0 -99
- package/backend/src/flowent/tools/sleep.py +0 -41
- package/backend/src/flowent/tools/todo.py +0 -51
- package/backend/src/flowent/workspace_store.py +0 -479
- 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 +0 -6
- 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_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 +0 -29
- package/backend/tests/integration/api/test_access_api.py +0 -182
- package/backend/tests/integration/api/test_assistant_api.py +0 -422
- package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
- package/backend/tests/integration/api/test_meta_api.py +0 -32
- package/backend/tests/integration/api/test_nodes_api.py +0 -787
- package/backend/tests/integration/api/test_prompts_api.py +0 -47
- package/backend/tests/integration/api/test_roles_api.py +0 -228
- package/backend/tests/integration/api/test_tabs_api.py +0 -688
- 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 +0 -822
- package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
- 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 +0 -552
- 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 +0 -132
- 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 +0 -570
- 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 +0 -185
- package/backend/tests/unit/providers/test_errors.py +0 -68
- package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
- package/backend/tests/unit/providers/test_openai_provider.py +0 -139
- package/backend/tests/unit/providers/test_openai_responses.py +0 -402
- package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
- package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
- 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/test_prompts_routes.py +0 -82
- package/backend/tests/unit/routes/test_providers_route.py +0 -370
- package/backend/tests/unit/routes/test_roles_routes.py +0 -539
- package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
- 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 +0 -1002
- 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 +0 -78
- 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 +0 -124
- 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 +0 -703
- package/backend/tests/unit/test_access.py +0 -45
- package/backend/tests/unit/test_cli.py +0 -102
- package/backend/tests/unit/test_graph_runtime.py +0 -72
- package/backend/tests/unit/test_network.py +0 -51
- package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
- package/backend/tests/unit/test_workspace_store.py +0 -228
- 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 +0 -228
- package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
- package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
- package/backend/tests/unit/tools/test_edit_tool.py +0 -115
- package/backend/tests/unit/tools/test_exec_tool.py +0 -81
- package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
- package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
- package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
- package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
- package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
- package/backend/tests/unit/tools/test_read_tool.py +0 -33
- package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
- package/backend/tests/unit/tools/test_todo_tool.py +0 -37
- package/backend/tests/unit/tools/test_tool_registry.py +0 -199
- package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
- package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
- package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
- package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
- package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
- package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
- package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
- package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
- package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
- package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
- package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
- package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
- package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
- package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
- package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
- package/dist/frontend/assets/index--o_0fv0N.css +0 -1
- package/dist/frontend/assets/index-C9HuekJm.js +0 -10
- package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
- package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
- package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
- package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
- package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
- package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
- package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
- package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
- package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
- package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
- package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
- package/dist/frontend/favicon.svg +0 -4
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from collections.abc import Iterable
|
|
4
|
-
from typing import Protocol
|
|
5
|
-
|
|
6
|
-
from flowent.models import ModelInfo
|
|
7
|
-
from flowent.providers.base_url import resolve_provider_base_url
|
|
8
|
-
from flowent.settings import (
|
|
9
|
-
PROVIDER_MODEL_SOURCE_OPTIONS,
|
|
10
|
-
ProviderConfig,
|
|
11
|
-
ProviderModelCatalogEntry,
|
|
12
|
-
build_model_context_window_tokens,
|
|
13
|
-
build_model_input_image,
|
|
14
|
-
build_model_output_image,
|
|
15
|
-
build_model_structured_output,
|
|
16
|
-
build_provider_headers,
|
|
17
|
-
build_provider_retry_429_delay_seconds,
|
|
18
|
-
serialize_provider_model_catalog_entry,
|
|
19
|
-
)
|
|
20
|
-
from flowent.settings import (
|
|
21
|
-
serialize_provider as serialize_full_provider,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class ProviderModelCatalogPayload(Protocol):
|
|
26
|
-
model: str
|
|
27
|
-
source: str
|
|
28
|
-
context_window_tokens: int | None
|
|
29
|
-
input_image: bool | None
|
|
30
|
-
output_image: bool | None
|
|
31
|
-
structured_output: bool | None
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def validate_provider_base_url_input(
|
|
35
|
-
provider_type: str,
|
|
36
|
-
base_url: str,
|
|
37
|
-
*,
|
|
38
|
-
required_message: str = "base_url is required",
|
|
39
|
-
) -> str:
|
|
40
|
-
raw_base_url = base_url.strip()
|
|
41
|
-
if not raw_base_url:
|
|
42
|
-
raise ValueError(required_message)
|
|
43
|
-
resolve_provider_base_url(provider_type, raw_base_url)
|
|
44
|
-
return raw_base_url
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def build_provider_model_catalog_entry(
|
|
48
|
-
payload: ProviderModelCatalogPayload,
|
|
49
|
-
*,
|
|
50
|
-
field_name_prefix: str = "models[]",
|
|
51
|
-
) -> ProviderModelCatalogEntry:
|
|
52
|
-
model = payload.model.strip()
|
|
53
|
-
if not model:
|
|
54
|
-
raise ValueError(f"{field_name_prefix}.model must not be empty")
|
|
55
|
-
|
|
56
|
-
source = payload.source.strip().lower()
|
|
57
|
-
if source not in PROVIDER_MODEL_SOURCE_OPTIONS:
|
|
58
|
-
raise ValueError(
|
|
59
|
-
f"{field_name_prefix}.source must be one of: discovered, manual"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
return ProviderModelCatalogEntry(
|
|
63
|
-
model=model,
|
|
64
|
-
source=source,
|
|
65
|
-
context_window_tokens=build_model_context_window_tokens(
|
|
66
|
-
payload.context_window_tokens,
|
|
67
|
-
field_name=f"{field_name_prefix}.context_window_tokens",
|
|
68
|
-
),
|
|
69
|
-
input_image=build_model_input_image(
|
|
70
|
-
payload.input_image,
|
|
71
|
-
field_name=f"{field_name_prefix}.input_image",
|
|
72
|
-
),
|
|
73
|
-
output_image=build_model_output_image(
|
|
74
|
-
payload.output_image,
|
|
75
|
-
field_name=f"{field_name_prefix}.output_image",
|
|
76
|
-
),
|
|
77
|
-
structured_output=build_model_structured_output(
|
|
78
|
-
payload.structured_output,
|
|
79
|
-
field_name=f"{field_name_prefix}.structured_output",
|
|
80
|
-
),
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def coerce_provider_model_catalog(
|
|
85
|
-
payloads: Iterable[ProviderModelCatalogPayload] | None,
|
|
86
|
-
*,
|
|
87
|
-
field_name_prefix: str = "models[]",
|
|
88
|
-
) -> list[ProviderModelCatalogEntry]:
|
|
89
|
-
entries: list[ProviderModelCatalogEntry] = []
|
|
90
|
-
seen_models: set[str] = set()
|
|
91
|
-
for payload in payloads or ():
|
|
92
|
-
entry = build_provider_model_catalog_entry(
|
|
93
|
-
payload,
|
|
94
|
-
field_name_prefix=field_name_prefix,
|
|
95
|
-
)
|
|
96
|
-
if entry.model in seen_models:
|
|
97
|
-
raise ValueError(f"{field_name_prefix}.model '{entry.model}' is duplicated")
|
|
98
|
-
seen_models.add(entry.model)
|
|
99
|
-
entries.append(entry)
|
|
100
|
-
return entries
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def build_provider_config(
|
|
104
|
-
*,
|
|
105
|
-
provider_id: str,
|
|
106
|
-
name: str,
|
|
107
|
-
provider_type: str,
|
|
108
|
-
base_url: str,
|
|
109
|
-
api_key: str = "",
|
|
110
|
-
raw_headers: object = None,
|
|
111
|
-
raw_retry_429_delay_seconds: object = 0,
|
|
112
|
-
models: list[ProviderModelCatalogEntry] | None = None,
|
|
113
|
-
base_url_required_message: str = "base_url is required",
|
|
114
|
-
) -> ProviderConfig:
|
|
115
|
-
return ProviderConfig(
|
|
116
|
-
id=provider_id,
|
|
117
|
-
name=name,
|
|
118
|
-
type=provider_type,
|
|
119
|
-
base_url=validate_provider_base_url_input(
|
|
120
|
-
provider_type,
|
|
121
|
-
base_url,
|
|
122
|
-
required_message=base_url_required_message,
|
|
123
|
-
),
|
|
124
|
-
api_key=api_key,
|
|
125
|
-
headers=build_provider_headers(raw_headers),
|
|
126
|
-
retry_429_delay_seconds=build_provider_retry_429_delay_seconds(
|
|
127
|
-
raw_retry_429_delay_seconds
|
|
128
|
-
),
|
|
129
|
-
models=list(models or []),
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def apply_provider_update(
|
|
134
|
-
provider: ProviderConfig,
|
|
135
|
-
*,
|
|
136
|
-
name: str | None = None,
|
|
137
|
-
provider_type: str | None = None,
|
|
138
|
-
base_url: str | None = None,
|
|
139
|
-
api_key: str | None = None,
|
|
140
|
-
raw_headers: object | None = None,
|
|
141
|
-
raw_retry_429_delay_seconds: object | None = None,
|
|
142
|
-
models: list[ProviderModelCatalogEntry] | None = None,
|
|
143
|
-
) -> ProviderConfig:
|
|
144
|
-
next_type = provider_type if provider_type is not None else provider.type
|
|
145
|
-
next_base_url = base_url.strip() if base_url is not None else provider.base_url
|
|
146
|
-
raw_base_url = validate_provider_base_url_input(next_type, next_base_url)
|
|
147
|
-
|
|
148
|
-
if name is not None:
|
|
149
|
-
provider.name = name
|
|
150
|
-
if provider_type is not None:
|
|
151
|
-
provider.type = provider_type
|
|
152
|
-
if base_url is not None or provider_type is not None:
|
|
153
|
-
provider.base_url = raw_base_url
|
|
154
|
-
if api_key is not None:
|
|
155
|
-
provider.api_key = api_key
|
|
156
|
-
if raw_headers is not None:
|
|
157
|
-
provider.headers = build_provider_headers(raw_headers)
|
|
158
|
-
if raw_retry_429_delay_seconds is not None:
|
|
159
|
-
provider.retry_429_delay_seconds = build_provider_retry_429_delay_seconds(
|
|
160
|
-
raw_retry_429_delay_seconds
|
|
161
|
-
)
|
|
162
|
-
if models is not None:
|
|
163
|
-
provider.models = list(models)
|
|
164
|
-
|
|
165
|
-
return provider
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def serialize_provider(
|
|
169
|
-
provider: ProviderConfig,
|
|
170
|
-
*,
|
|
171
|
-
include_api_key: bool = True,
|
|
172
|
-
) -> dict[str, object]:
|
|
173
|
-
data = serialize_full_provider(provider)
|
|
174
|
-
if not include_api_key:
|
|
175
|
-
data.pop("api_key", None)
|
|
176
|
-
return data
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def serialize_discovered_model_catalog_entry(model: ModelInfo) -> dict[str, object]:
|
|
180
|
-
return serialize_provider_model_catalog_entry(
|
|
181
|
-
ProviderModelCatalogEntry(
|
|
182
|
-
model=model.id,
|
|
183
|
-
source="discovered",
|
|
184
|
-
context_window_tokens=model.context_window_tokens,
|
|
185
|
-
input_image=model.capabilities.input_image,
|
|
186
|
-
output_image=model.capabilities.output_image,
|
|
187
|
-
structured_output=model.capabilities.structured_output,
|
|
188
|
-
)
|
|
189
|
-
)
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from flowent.image_assets import (
|
|
6
|
-
encode_image_asset_as_base64,
|
|
7
|
-
encode_image_asset_as_data_url,
|
|
8
|
-
)
|
|
9
|
-
from flowent.models import (
|
|
10
|
-
ContentPart,
|
|
11
|
-
TextPart,
|
|
12
|
-
content_parts_to_text,
|
|
13
|
-
deserialize_content_parts,
|
|
14
|
-
has_image_parts,
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def parse_message_content_parts(content: Any) -> list[ContentPart]:
|
|
19
|
-
if isinstance(content, str):
|
|
20
|
-
return [TextPart(text=content)] if content else []
|
|
21
|
-
return deserialize_content_parts(content)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def collapse_parts_to_text(content: Any) -> str:
|
|
25
|
-
if isinstance(content, str):
|
|
26
|
-
return content
|
|
27
|
-
return content_parts_to_text(parse_message_content_parts(content))
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def has_image_content(content: Any) -> bool:
|
|
31
|
-
return has_image_parts(parse_message_content_parts(content))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def to_openai_chat_content(
|
|
35
|
-
content: Any, *, allow_images: bool
|
|
36
|
-
) -> str | list[dict[str, Any]]:
|
|
37
|
-
parts = parse_message_content_parts(content)
|
|
38
|
-
if not parts:
|
|
39
|
-
return ""
|
|
40
|
-
if not allow_images or not has_image_parts(parts):
|
|
41
|
-
return content_parts_to_text(parts)
|
|
42
|
-
payload: list[dict[str, Any]] = []
|
|
43
|
-
for part in parts:
|
|
44
|
-
if isinstance(part, TextPart):
|
|
45
|
-
payload.append({"type": "text", "text": part.text})
|
|
46
|
-
continue
|
|
47
|
-
_, data_url = encode_image_asset_as_data_url(part.asset_id)
|
|
48
|
-
payload.append(
|
|
49
|
-
{
|
|
50
|
-
"type": "image_url",
|
|
51
|
-
"image_url": {"url": data_url},
|
|
52
|
-
}
|
|
53
|
-
)
|
|
54
|
-
return payload
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def to_openai_responses_content(
|
|
58
|
-
content: Any, *, allow_images: bool
|
|
59
|
-
) -> str | list[dict[str, Any]]:
|
|
60
|
-
parts = parse_message_content_parts(content)
|
|
61
|
-
if not parts:
|
|
62
|
-
return ""
|
|
63
|
-
if not allow_images or not has_image_parts(parts):
|
|
64
|
-
return content_parts_to_text(parts)
|
|
65
|
-
payload: list[dict[str, Any]] = []
|
|
66
|
-
for part in parts:
|
|
67
|
-
if isinstance(part, TextPart):
|
|
68
|
-
payload.append({"type": "input_text", "text": part.text})
|
|
69
|
-
continue
|
|
70
|
-
_, data_url = encode_image_asset_as_data_url(part.asset_id)
|
|
71
|
-
payload.append({"type": "input_image", "image_url": data_url})
|
|
72
|
-
return payload
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def to_anthropic_content(
|
|
76
|
-
content: Any, *, allow_images: bool
|
|
77
|
-
) -> str | list[dict[str, Any]]:
|
|
78
|
-
parts = parse_message_content_parts(content)
|
|
79
|
-
if not parts:
|
|
80
|
-
return ""
|
|
81
|
-
if not allow_images or not has_image_parts(parts):
|
|
82
|
-
return content_parts_to_text(parts)
|
|
83
|
-
payload: list[dict[str, Any]] = []
|
|
84
|
-
for part in parts:
|
|
85
|
-
if isinstance(part, TextPart):
|
|
86
|
-
payload.append({"type": "text", "text": part.text})
|
|
87
|
-
continue
|
|
88
|
-
asset, encoded = encode_image_asset_as_base64(part.asset_id)
|
|
89
|
-
payload.append(
|
|
90
|
-
{
|
|
91
|
-
"type": "image",
|
|
92
|
-
"source": {
|
|
93
|
-
"type": "base64",
|
|
94
|
-
"media_type": asset.mime_type,
|
|
95
|
-
"data": encoded,
|
|
96
|
-
},
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
return payload
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def to_gemini_parts(content: Any, *, allow_images: bool) -> list[dict[str, Any]]:
|
|
103
|
-
parts = parse_message_content_parts(content)
|
|
104
|
-
if not parts:
|
|
105
|
-
return [{"text": ""}]
|
|
106
|
-
if not allow_images or not has_image_parts(parts):
|
|
107
|
-
return [{"text": content_parts_to_text(parts)}]
|
|
108
|
-
payload: list[dict[str, Any]] = []
|
|
109
|
-
for part in parts:
|
|
110
|
-
if isinstance(part, TextPart):
|
|
111
|
-
payload.append({"text": part.text})
|
|
112
|
-
continue
|
|
113
|
-
asset, encoded = encode_image_asset_as_base64(part.asset_id)
|
|
114
|
-
payload.append(
|
|
115
|
-
{
|
|
116
|
-
"inlineData": {
|
|
117
|
-
"mimeType": asset.mime_type,
|
|
118
|
-
"data": encoded,
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
)
|
|
122
|
-
return payload
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import re
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from flowent.network import truncate_text
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class LLMProviderError(RuntimeError):
|
|
11
|
-
def __init__(
|
|
12
|
-
self,
|
|
13
|
-
message: str,
|
|
14
|
-
*,
|
|
15
|
-
transient: bool,
|
|
16
|
-
status_code: int | None = None,
|
|
17
|
-
) -> None:
|
|
18
|
-
super().__init__(message)
|
|
19
|
-
self.transient = transient
|
|
20
|
-
self.status_code = status_code
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def is_transient_status_code(status_code: int) -> bool:
|
|
24
|
-
return status_code == 429 or 500 <= status_code < 600
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def build_status_error(
|
|
28
|
-
*,
|
|
29
|
-
provider_name: str,
|
|
30
|
-
provider_type: str,
|
|
31
|
-
model: str,
|
|
32
|
-
base_url: str,
|
|
33
|
-
status_code: int,
|
|
34
|
-
body: str,
|
|
35
|
-
) -> LLMProviderError:
|
|
36
|
-
return LLMProviderError(
|
|
37
|
-
(
|
|
38
|
-
"LLM API error\n"
|
|
39
|
-
f"Provider: {provider_name}\n"
|
|
40
|
-
f"Type: {provider_type}\n"
|
|
41
|
-
f"Model: {model}\n"
|
|
42
|
-
f"Base URL: {base_url}\n"
|
|
43
|
-
f"Status: {status_code}\n"
|
|
44
|
-
f"Detail: {_normalize_status_detail(body)}"
|
|
45
|
-
),
|
|
46
|
-
transient=is_transient_status_code(status_code),
|
|
47
|
-
status_code=status_code,
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def build_network_error(
|
|
52
|
-
*,
|
|
53
|
-
provider_name: str,
|
|
54
|
-
provider_type: str,
|
|
55
|
-
model: str,
|
|
56
|
-
base_url: str,
|
|
57
|
-
error: Exception,
|
|
58
|
-
) -> LLMProviderError:
|
|
59
|
-
return LLMProviderError(
|
|
60
|
-
(
|
|
61
|
-
"LLM API network error\n"
|
|
62
|
-
f"Provider: {provider_name}\n"
|
|
63
|
-
f"Type: {provider_type}\n"
|
|
64
|
-
f"Model: {model}\n"
|
|
65
|
-
f"Base URL: {base_url}\n"
|
|
66
|
-
f"Error: {_normalize_network_error(error)}"
|
|
67
|
-
),
|
|
68
|
-
transient=True,
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def build_configuration_error(
|
|
73
|
-
*,
|
|
74
|
-
provider_name: str,
|
|
75
|
-
provider_type: str,
|
|
76
|
-
model: str,
|
|
77
|
-
base_url: str,
|
|
78
|
-
detail: str,
|
|
79
|
-
) -> LLMProviderError:
|
|
80
|
-
return LLMProviderError(
|
|
81
|
-
(
|
|
82
|
-
"LLM configuration error\n"
|
|
83
|
-
f"Provider: {provider_name}\n"
|
|
84
|
-
f"Type: {provider_type}\n"
|
|
85
|
-
f"Model: {model}\n"
|
|
86
|
-
f"Base URL: {base_url}\n"
|
|
87
|
-
f"Detail: {_compact_text(detail)}"
|
|
88
|
-
),
|
|
89
|
-
transient=False,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def build_access_blocked_error(
|
|
94
|
-
*,
|
|
95
|
-
provider_name: str,
|
|
96
|
-
provider_type: str,
|
|
97
|
-
model: str,
|
|
98
|
-
base_url: str,
|
|
99
|
-
status_code: int | None = None,
|
|
100
|
-
detail: str,
|
|
101
|
-
) -> LLMProviderError:
|
|
102
|
-
status_line = f"Status: {status_code}\n" if status_code is not None else ""
|
|
103
|
-
return LLMProviderError(
|
|
104
|
-
(
|
|
105
|
-
"LLM API access blocked\n"
|
|
106
|
-
f"Provider: {provider_name}\n"
|
|
107
|
-
f"Type: {provider_type}\n"
|
|
108
|
-
f"Model: {model}\n"
|
|
109
|
-
f"Base URL: {base_url}\n"
|
|
110
|
-
f"{status_line}"
|
|
111
|
-
f"Detail: {_normalize_access_blocked_detail(detail)}"
|
|
112
|
-
),
|
|
113
|
-
transient=False,
|
|
114
|
-
status_code=status_code,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def _normalize_status_detail(body: str) -> str:
|
|
119
|
-
stripped = body.strip()
|
|
120
|
-
if not stripped:
|
|
121
|
-
return "Provider returned an empty error response"
|
|
122
|
-
if _looks_like_html(stripped):
|
|
123
|
-
return "Provider returned a non-API HTML response"
|
|
124
|
-
parsed = _parse_json_body(stripped)
|
|
125
|
-
if parsed is not None:
|
|
126
|
-
detail = _extract_detail(parsed)
|
|
127
|
-
if detail is not None:
|
|
128
|
-
return detail
|
|
129
|
-
first_line = stripped.splitlines()[0]
|
|
130
|
-
detail = _compact_text(first_line)
|
|
131
|
-
if not detail or detail.lower().startswith("traceback"):
|
|
132
|
-
return "Provider returned an unexpected error response"
|
|
133
|
-
return detail
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _normalize_network_error(error: Exception) -> str:
|
|
137
|
-
detail = str(error).strip()
|
|
138
|
-
detail = re.sub(r"^[A-Za-z_][A-Za-z0-9_]*(?:Error|Exception):\s*", "", detail)
|
|
139
|
-
detail = re.sub(r"^Failed to perform,\s*", "", detail)
|
|
140
|
-
detail = re.sub(r"^curl:\s*\(\d+\)\s*", "", detail, flags=re.IGNORECASE)
|
|
141
|
-
detail = re.sub(r"^curl\s+error:\s*", "", detail, flags=re.IGNORECASE)
|
|
142
|
-
detail = re.sub(r"See https?://\S+ for more details\.?", "", detail)
|
|
143
|
-
detail = _compact_text(detail)
|
|
144
|
-
if not detail:
|
|
145
|
-
return "Request failed before the provider returned a response"
|
|
146
|
-
return detail
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _normalize_access_blocked_detail(detail: str) -> str:
|
|
150
|
-
stripped = detail.strip()
|
|
151
|
-
if not stripped:
|
|
152
|
-
return "Challenge or interstitial response from upstream"
|
|
153
|
-
if _looks_like_html(stripped):
|
|
154
|
-
return "Challenge or interstitial HTML response from upstream"
|
|
155
|
-
normalized = _compact_text(stripped)
|
|
156
|
-
if not normalized:
|
|
157
|
-
return "Challenge or interstitial response from upstream"
|
|
158
|
-
return normalized
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def _parse_json_body(body: str) -> Any | None:
|
|
162
|
-
try:
|
|
163
|
-
return json.loads(body)
|
|
164
|
-
except json.JSONDecodeError:
|
|
165
|
-
return None
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def _extract_detail(value: Any) -> str | None:
|
|
169
|
-
if isinstance(value, str):
|
|
170
|
-
return _normalize_detail_text(value)
|
|
171
|
-
if isinstance(value, list):
|
|
172
|
-
for item in value:
|
|
173
|
-
detail = _extract_detail(item)
|
|
174
|
-
if detail is not None:
|
|
175
|
-
return detail
|
|
176
|
-
return None
|
|
177
|
-
if not isinstance(value, dict):
|
|
178
|
-
return None
|
|
179
|
-
|
|
180
|
-
nested_error = value.get("error")
|
|
181
|
-
if nested_error is not None:
|
|
182
|
-
detail = _extract_detail(nested_error)
|
|
183
|
-
if detail is not None:
|
|
184
|
-
return detail
|
|
185
|
-
|
|
186
|
-
for key in (
|
|
187
|
-
"message",
|
|
188
|
-
"detail",
|
|
189
|
-
"error_description",
|
|
190
|
-
"title",
|
|
191
|
-
"status",
|
|
192
|
-
"code",
|
|
193
|
-
):
|
|
194
|
-
detail = _extract_detail(value.get(key))
|
|
195
|
-
if detail is not None:
|
|
196
|
-
return detail
|
|
197
|
-
|
|
198
|
-
for key in ("details", "errors"):
|
|
199
|
-
detail = _extract_detail(value.get(key))
|
|
200
|
-
if detail is not None:
|
|
201
|
-
return detail
|
|
202
|
-
|
|
203
|
-
return None
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def _normalize_detail_text(value: str) -> str | None:
|
|
207
|
-
normalized = _compact_text(value)
|
|
208
|
-
if not normalized:
|
|
209
|
-
return None
|
|
210
|
-
if normalized.lower().startswith("traceback"):
|
|
211
|
-
return "Upstream returned an unexpected error response"
|
|
212
|
-
return normalized
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _compact_text(value: str, *, limit: int = 240) -> str:
|
|
216
|
-
return truncate_text(re.sub(r"\s+", " ", value).strip(), limit=limit)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def _looks_like_html(value: str) -> bool:
|
|
220
|
-
lowered = value.lstrip().lower()
|
|
221
|
-
return lowered.startswith("<!doctype html") or (
|
|
222
|
-
lowered.startswith("<") and ">" in lowered
|
|
223
|
-
)
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import threading
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from loguru import logger
|
|
8
|
-
|
|
9
|
-
from flowent.models import LLMResponse, ModelInfo
|
|
10
|
-
from flowent.providers import LLMProvider
|
|
11
|
-
from flowent.providers.errors import build_configuration_error
|
|
12
|
-
from flowent.settings import merge_model_params
|
|
13
|
-
|
|
14
|
-
MODEL_DIRECTORY_TIMEOUT_SECONDS = 120.0
|
|
15
|
-
|
|
16
|
-
ProviderCacheKey = tuple[
|
|
17
|
-
str,
|
|
18
|
-
str,
|
|
19
|
-
str,
|
|
20
|
-
str,
|
|
21
|
-
tuple[tuple[str, str], ...],
|
|
22
|
-
str,
|
|
23
|
-
str,
|
|
24
|
-
int,
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class ProviderGateway:
|
|
29
|
-
def __init__(self) -> None:
|
|
30
|
-
self._cache: dict[ProviderCacheKey, LLMProvider] = {}
|
|
31
|
-
self._lock = threading.Lock()
|
|
32
|
-
|
|
33
|
-
def invalidate_cache(self) -> None:
|
|
34
|
-
with self._lock:
|
|
35
|
-
self._cache.clear()
|
|
36
|
-
|
|
37
|
-
def chat(
|
|
38
|
-
self,
|
|
39
|
-
messages: list[dict[str, Any]],
|
|
40
|
-
tools: list[dict[str, Any]] | None = None,
|
|
41
|
-
on_chunk: Callable[[str, str], None] | None = None,
|
|
42
|
-
register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
|
|
43
|
-
role_name: str | None = None,
|
|
44
|
-
) -> LLMResponse:
|
|
45
|
-
from flowent.settings import find_role, get_settings
|
|
46
|
-
|
|
47
|
-
settings = get_settings()
|
|
48
|
-
role_cfg = find_role(settings, role_name) if role_name else None
|
|
49
|
-
provider = self._resolve(settings=settings, role_cfg=role_cfg)
|
|
50
|
-
model_params = merge_model_params(
|
|
51
|
-
settings.model.params,
|
|
52
|
-
role_cfg.model_params if role_cfg is not None else None,
|
|
53
|
-
)
|
|
54
|
-
return provider.chat(
|
|
55
|
-
messages,
|
|
56
|
-
tools,
|
|
57
|
-
on_chunk,
|
|
58
|
-
register_interrupt,
|
|
59
|
-
model_params,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
def list_models_for(
|
|
63
|
-
self,
|
|
64
|
-
provider_id: str,
|
|
65
|
-
register_interrupt: Callable[[Callable[[], None] | None], None] | None = None,
|
|
66
|
-
) -> list[ModelInfo]:
|
|
67
|
-
from flowent.providers.registry import create_provider
|
|
68
|
-
from flowent.settings import find_provider, get_settings
|
|
69
|
-
|
|
70
|
-
settings = get_settings()
|
|
71
|
-
cfg = find_provider(settings, provider_id)
|
|
72
|
-
if cfg is None:
|
|
73
|
-
return []
|
|
74
|
-
|
|
75
|
-
provider = create_provider(
|
|
76
|
-
provider_type=cfg.type,
|
|
77
|
-
base_url=cfg.base_url,
|
|
78
|
-
api_key=cfg.api_key,
|
|
79
|
-
headers=cfg.headers,
|
|
80
|
-
model="",
|
|
81
|
-
provider_name=cfg.name,
|
|
82
|
-
request_timeout_seconds=MODEL_DIRECTORY_TIMEOUT_SECONDS,
|
|
83
|
-
)
|
|
84
|
-
return provider.list_models(register_interrupt)
|
|
85
|
-
|
|
86
|
-
def _resolve(
|
|
87
|
-
self,
|
|
88
|
-
*,
|
|
89
|
-
settings=None,
|
|
90
|
-
role_cfg=None,
|
|
91
|
-
role_name: str | None = None,
|
|
92
|
-
) -> LLMProvider:
|
|
93
|
-
from flowent.providers.registry import create_provider
|
|
94
|
-
from flowent.settings import find_provider, find_role, get_settings
|
|
95
|
-
|
|
96
|
-
if settings is None:
|
|
97
|
-
settings = get_settings()
|
|
98
|
-
provider_id = settings.model.active_provider_id
|
|
99
|
-
model = settings.model.active_model
|
|
100
|
-
|
|
101
|
-
if role_cfg is None and role_name:
|
|
102
|
-
role_cfg = find_role(settings, role_name)
|
|
103
|
-
if (
|
|
104
|
-
role_cfg is not None
|
|
105
|
-
and role_cfg.model is not None
|
|
106
|
-
and role_cfg.model.provider_id
|
|
107
|
-
and role_cfg.model.model
|
|
108
|
-
):
|
|
109
|
-
provider_id = role_cfg.model.provider_id
|
|
110
|
-
model = role_cfg.model.model
|
|
111
|
-
|
|
112
|
-
if not provider_id:
|
|
113
|
-
raise RuntimeError("No active provider configured")
|
|
114
|
-
|
|
115
|
-
cfg = find_provider(settings, provider_id)
|
|
116
|
-
if cfg is None:
|
|
117
|
-
raise RuntimeError(f"Provider '{provider_id}' not found")
|
|
118
|
-
|
|
119
|
-
if not model.strip():
|
|
120
|
-
raise build_configuration_error(
|
|
121
|
-
provider_name=cfg.name,
|
|
122
|
-
provider_type=cfg.type,
|
|
123
|
-
model=model,
|
|
124
|
-
base_url=cfg.base_url,
|
|
125
|
-
detail="No active model configured",
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
provider_type = cfg.type
|
|
129
|
-
base_url = cfg.base_url
|
|
130
|
-
api_key = cfg.api_key
|
|
131
|
-
provider_name = cfg.name
|
|
132
|
-
request_timeout_seconds = settings.model.timeout_ms / 1000
|
|
133
|
-
cache_key = (
|
|
134
|
-
cfg.id,
|
|
135
|
-
provider_type,
|
|
136
|
-
base_url,
|
|
137
|
-
api_key,
|
|
138
|
-
tuple(sorted(cfg.headers.items())),
|
|
139
|
-
provider_name,
|
|
140
|
-
model,
|
|
141
|
-
settings.model.timeout_ms,
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
with self._lock:
|
|
145
|
-
if cache_key in self._cache:
|
|
146
|
-
return self._cache[cache_key]
|
|
147
|
-
|
|
148
|
-
logger.debug(
|
|
149
|
-
"ProviderGateway resolved: name={}, type={}, model={}",
|
|
150
|
-
provider_name,
|
|
151
|
-
provider_type,
|
|
152
|
-
model,
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
provider = create_provider(
|
|
156
|
-
provider_type=provider_type,
|
|
157
|
-
base_url=base_url,
|
|
158
|
-
api_key=api_key,
|
|
159
|
-
headers=cfg.headers,
|
|
160
|
-
model=model,
|
|
161
|
-
provider_name=provider_name,
|
|
162
|
-
request_timeout_seconds=request_timeout_seconds,
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
self._cache[cache_key] = provider
|
|
166
|
-
return provider
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
gateway = ProviderGateway()
|