flowent 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. flowent-0.0.4/PKG-INFO +20 -0
  2. flowent-0.0.4/pyproject.toml +57 -0
  3. flowent-0.0.4/src/flowent/__init__.py +3 -0
  4. flowent-0.0.4/src/flowent/_version.py +7 -0
  5. flowent-0.0.4/src/flowent/access.py +247 -0
  6. flowent-0.0.4/src/flowent/agent.py +2808 -0
  7. flowent-0.0.4/src/flowent/assistant_commands.py +106 -0
  8. flowent-0.0.4/src/flowent/channels/__init__.py +3 -0
  9. flowent-0.0.4/src/flowent/channels/telegram.py +615 -0
  10. flowent-0.0.4/src/flowent/cli.py +85 -0
  11. flowent-0.0.4/src/flowent/config.py +14 -0
  12. flowent-0.0.4/src/flowent/dev.py +3 -0
  13. flowent-0.0.4/src/flowent/events.py +157 -0
  14. flowent-0.0.4/src/flowent/graph_runtime.py +60 -0
  15. flowent-0.0.4/src/flowent/graph_service.py +1346 -0
  16. flowent-0.0.4/src/flowent/image_assets.py +356 -0
  17. flowent-0.0.4/src/flowent/logging.py +155 -0
  18. flowent-0.0.4/src/flowent/main.py +124 -0
  19. flowent-0.0.4/src/flowent/mcp_service.py +1904 -0
  20. flowent-0.0.4/src/flowent/model_metadata.py +98 -0
  21. flowent-0.0.4/src/flowent/models/__init__.py +121 -0
  22. flowent-0.0.4/src/flowent/models/agent.py +33 -0
  23. flowent-0.0.4/src/flowent/models/base.py +24 -0
  24. flowent-0.0.4/src/flowent/models/blueprint.py +176 -0
  25. flowent-0.0.4/src/flowent/models/content.py +164 -0
  26. flowent-0.0.4/src/flowent/models/delta.py +44 -0
  27. flowent-0.0.4/src/flowent/models/event.py +51 -0
  28. flowent-0.0.4/src/flowent/models/graph.py +437 -0
  29. flowent-0.0.4/src/flowent/models/history.py +214 -0
  30. flowent-0.0.4/src/flowent/models/llm.py +61 -0
  31. flowent-0.0.4/src/flowent/models/message.py +27 -0
  32. flowent-0.0.4/src/flowent/models/tab.py +48 -0
  33. flowent-0.0.4/src/flowent/models/todo.py +10 -0
  34. flowent-0.0.4/src/flowent/network.py +146 -0
  35. flowent-0.0.4/src/flowent/prompts/__init__.py +67 -0
  36. flowent-0.0.4/src/flowent/prompts/common.py +250 -0
  37. flowent-0.0.4/src/flowent/prompts/steward.py +64 -0
  38. flowent-0.0.4/src/flowent/providers/__init__.py +23 -0
  39. flowent-0.0.4/src/flowent/providers/anthropic.py +468 -0
  40. flowent-0.0.4/src/flowent/providers/base_url.py +60 -0
  41. flowent-0.0.4/src/flowent/providers/configuration.py +182 -0
  42. flowent-0.0.4/src/flowent/providers/content.py +122 -0
  43. flowent-0.0.4/src/flowent/providers/errors.py +223 -0
  44. flowent-0.0.4/src/flowent/providers/gateway.py +169 -0
  45. flowent-0.0.4/src/flowent/providers/gemini.py +447 -0
  46. flowent-0.0.4/src/flowent/providers/headers.py +20 -0
  47. flowent-0.0.4/src/flowent/providers/management.py +96 -0
  48. flowent-0.0.4/src/flowent/providers/ollama.py +293 -0
  49. flowent-0.0.4/src/flowent/providers/openai.py +422 -0
  50. flowent-0.0.4/src/flowent/providers/openai_responses.py +655 -0
  51. flowent-0.0.4/src/flowent/providers/registry.py +144 -0
  52. flowent-0.0.4/src/flowent/providers/sse.py +31 -0
  53. flowent-0.0.4/src/flowent/providers/thinking.py +79 -0
  54. flowent-0.0.4/src/flowent/registry.py +73 -0
  55. flowent-0.0.4/src/flowent/role_management.py +255 -0
  56. flowent-0.0.4/src/flowent/routes/__init__.py +30 -0
  57. flowent-0.0.4/src/flowent/routes/access.py +48 -0
  58. flowent-0.0.4/src/flowent/routes/assistant.py +155 -0
  59. flowent-0.0.4/src/flowent/routes/image_assets.py +33 -0
  60. flowent-0.0.4/src/flowent/routes/mcp.py +125 -0
  61. flowent-0.0.4/src/flowent/routes/meta.py +28 -0
  62. flowent-0.0.4/src/flowent/routes/nodes.py +365 -0
  63. flowent-0.0.4/src/flowent/routes/prompts.py +46 -0
  64. flowent-0.0.4/src/flowent/routes/providers_route.py +364 -0
  65. flowent-0.0.4/src/flowent/routes/roles.py +207 -0
  66. flowent-0.0.4/src/flowent/routes/settings.py +324 -0
  67. flowent-0.0.4/src/flowent/routes/stats.py +229 -0
  68. flowent-0.0.4/src/flowent/routes/tabs.py +292 -0
  69. flowent-0.0.4/src/flowent/routes/ws.py +33 -0
  70. flowent-0.0.4/src/flowent/runtime.py +188 -0
  71. flowent-0.0.4/src/flowent/sandbox.py +45 -0
  72. flowent-0.0.4/src/flowent/security.py +42 -0
  73. flowent-0.0.4/src/flowent/settings.py +2467 -0
  74. flowent-0.0.4/src/flowent/settings_management.py +286 -0
  75. flowent-0.0.4/src/flowent/state_db.py +120 -0
  76. flowent-0.0.4/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +1 -0
  77. flowent-0.0.4/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +1 -0
  78. flowent-0.0.4/src/flowent/static/assets/HomePage-C0hAx9_l.js +3 -0
  79. flowent-0.0.4/src/flowent/static/assets/McpPage-DkrYLvBv.js +7 -0
  80. flowent-0.0.4/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +1 -0
  81. flowent-0.0.4/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +1 -0
  82. flowent-0.0.4/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +3 -0
  83. flowent-0.0.4/src/flowent/static/assets/RolesPage-CqcclGRw.js +1 -0
  84. flowent-0.0.4/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +3 -0
  85. flowent-0.0.4/src/flowent/static/assets/StatsPage-BX9khYzu.js +1 -0
  86. flowent-0.0.4/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +1 -0
  87. flowent-0.0.4/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
  88. flowent-0.0.4/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
  89. flowent-0.0.4/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +1 -0
  90. flowent-0.0.4/src/flowent/static/assets/badge-74-3jsCg.js +1 -0
  91. flowent-0.0.4/src/flowent/static/assets/constants-XUzFf6i1.js +1 -0
  92. flowent-0.0.4/src/flowent/static/assets/datetime-m6_O_Ci9.js +1 -0
  93. flowent-0.0.4/src/flowent/static/assets/dialog-BeGSweF6.js +1 -0
  94. flowent-0.0.4/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  95. flowent-0.0.4/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
  96. flowent-0.0.4/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
  97. flowent-0.0.4/src/flowent/static/assets/index-BHC1Vhy8.css +1 -0
  98. flowent-0.0.4/src/flowent/static/assets/index-CL1ALZ3r.js +10 -0
  99. flowent-0.0.4/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
  100. flowent-0.0.4/src/flowent/static/assets/markdown-vendor-DVdy_w12.js +29 -0
  101. flowent-0.0.4/src/flowent/static/assets/modelParams-CaHd0903.js +1 -0
  102. flowent-0.0.4/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
  103. flowent-0.0.4/src/flowent/static/assets/roles-2OLDeTc5.js +1 -0
  104. flowent-0.0.4/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  105. flowent-0.0.4/src/flowent/static/assets/select-DL_LPeDj.js +1 -0
  106. flowent-0.0.4/src/flowent/static/assets/shared-CMxbpLeQ.js +1 -0
  107. flowent-0.0.4/src/flowent/static/assets/triState-DEr3NkXV.js +1 -0
  108. flowent-0.0.4/src/flowent/static/assets/ui-vendor-Dg9NNnWX.js +51 -0
  109. flowent-0.0.4/src/flowent/static/favicon.svg +4 -0
  110. flowent-0.0.4/src/flowent/static/index.html +36 -0
  111. flowent-0.0.4/src/flowent/stats_service.py +218 -0
  112. flowent-0.0.4/src/flowent/tools/__init__.py +201 -0
  113. flowent-0.0.4/src/flowent/tools/connect.py +156 -0
  114. flowent-0.0.4/src/flowent/tools/contacts.py +22 -0
  115. flowent-0.0.4/src/flowent/tools/create_agent.py +270 -0
  116. flowent-0.0.4/src/flowent/tools/create_tab.py +59 -0
  117. flowent-0.0.4/src/flowent/tools/delete_tab.py +39 -0
  118. flowent-0.0.4/src/flowent/tools/edit.py +142 -0
  119. flowent-0.0.4/src/flowent/tools/exec.py +117 -0
  120. flowent-0.0.4/src/flowent/tools/fetch.py +85 -0
  121. flowent-0.0.4/src/flowent/tools/idle.py +27 -0
  122. flowent-0.0.4/src/flowent/tools/list_roles.py +50 -0
  123. flowent-0.0.4/src/flowent/tools/list_tabs.py +96 -0
  124. flowent-0.0.4/src/flowent/tools/list_tools.py +24 -0
  125. flowent-0.0.4/src/flowent/tools/manage_prompts.py +102 -0
  126. flowent-0.0.4/src/flowent/tools/manage_providers.py +220 -0
  127. flowent-0.0.4/src/flowent/tools/manage_roles.py +275 -0
  128. flowent-0.0.4/src/flowent/tools/manage_settings.py +346 -0
  129. flowent-0.0.4/src/flowent/tools/mcp.py +199 -0
  130. flowent-0.0.4/src/flowent/tools/read.py +152 -0
  131. flowent-0.0.4/src/flowent/tools/send.py +50 -0
  132. flowent-0.0.4/src/flowent/tools/set_permissions.py +84 -0
  133. flowent-0.0.4/src/flowent/tools/sleep.py +41 -0
  134. flowent-0.0.4/src/flowent/tools/todo.py +51 -0
  135. flowent-0.0.4/src/flowent/workspace_store.py +479 -0
flowent-0.0.4/PKG-INFO ADDED
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: flowent
3
+ Version: 0.0.4
4
+ Summary: A workflow orchestration platform for multi-agent collaboration.
5
+ Author: ImFeH2
6
+ Author-email: ImFeH2 <i@feh2.im>
7
+ License-Expression: Apache-2.0
8
+ Requires-Dist: curl-cffi>=0.15.0
9
+ Requires-Dist: fastapi[standard]>=0.136.1
10
+ Requires-Dist: httpx>=0.28.0
11
+ Requires-Dist: itsdangerous>=2.2.0
12
+ Requires-Dist: loguru>=0.7.0
13
+ Requires-Dist: pydantic-settings>=2.12.0
14
+ Requires-Dist: python-dotenv>=1.2.1
15
+ Requires-Dist: python-multipart>=0.0.22
16
+ Requires-Dist: uvicorn>=0.46.0
17
+ Requires-Python: >=3.12, <3.14
18
+ Project-URL: Homepage, https://github.com/ImFeH2/flowent
19
+ Project-URL: Issues, https://github.com/ImFeH2/flowent/issues
20
+ Project-URL: Repository, https://github.com/ImFeH2/flowent
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "flowent"
3
+ version = "0.0.4"
4
+ description = "A workflow orchestration platform for multi-agent collaboration."
5
+ authors = [
6
+ { name = "ImFeH2", email = "i@feh2.im" }
7
+ ]
8
+ requires-python = ">=3.12,<3.14"
9
+ license = "Apache-2.0"
10
+ dependencies = [
11
+ "curl-cffi>=0.15.0",
12
+ "fastapi[standard]>=0.136.1",
13
+ "httpx>=0.28.0",
14
+ "itsdangerous>=2.2.0",
15
+ "loguru>=0.7.0",
16
+ "pydantic-settings>=2.12.0",
17
+ "python-dotenv>=1.2.1",
18
+ "python-multipart>=0.0.22",
19
+ "uvicorn>=0.46.0",
20
+ ]
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "httpx>=0.28.1",
25
+ "mypy>=1.19.1",
26
+ "pre-commit>=4.5.1",
27
+ "pytest>=9.0.3",
28
+ "ruff>=0.15.12",
29
+ ]
30
+
31
+ [project.scripts]
32
+ flowent = "flowent:main"
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/ImFeH2/flowent"
36
+ Repository = "https://github.com/ImFeH2/flowent"
37
+ Issues = "https://github.com/ImFeH2/flowent/issues"
38
+
39
+ [build-system]
40
+ requires = ["uv_build>=0.8.14,<0.9.0"]
41
+ build-backend = "uv_build"
42
+
43
+ [tool.ruff]
44
+ target-version = "py312"
45
+
46
+ [tool.ruff.lint]
47
+ select = ["E", "W", "F", "I", "UP", "B", "SIM", "N", "RUF"]
48
+ ignore = ["E501"]
49
+
50
+ [tool.mypy]
51
+ python_version = "3.12"
52
+
53
+ [tool.pytest.ini_options]
54
+ testpaths = ["tests"]
55
+ python_files = ["test_*.py"]
56
+ python_classes = ["Test*"]
57
+ python_functions = ["test_*"]
@@ -0,0 +1,3 @@
1
+ from flowent.cli import main
2
+
3
+ __all__ = ["main"]
@@ -0,0 +1,7 @@
1
+ from importlib.metadata import PackageNotFoundError
2
+ from importlib.metadata import version as _pkg_version
3
+
4
+ try:
5
+ __version__ = _pkg_version("flowent")
6
+ except PackageNotFoundError:
7
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1,247 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import secrets
5
+ import threading
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from fastapi import Request
12
+ from loguru import logger
13
+ from starlette.middleware.base import BaseHTTPMiddleware
14
+ from starlette.responses import JSONResponse
15
+ from starlette.websockets import WebSocket
16
+
17
+ from flowent.settings import AccessSettings, Settings, get_settings, save_settings
18
+
19
+ ACCESS_SESSION_KEY = "admin_session_generation"
20
+ ACCESS_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
21
+ ACCESS_PUBLIC_PATHS = frozenset(
22
+ {
23
+ "/api/access/state",
24
+ "/api/access/login",
25
+ "/api/access/logout",
26
+ }
27
+ )
28
+
29
+
30
+ @dataclass
31
+ class _AccessRuntimeState:
32
+ bootstrap_generated: bool = False
33
+ live_signature: tuple[bool, int, str] | None = None
34
+
35
+
36
+ _runtime_state = _AccessRuntimeState()
37
+ _runtime_lock = threading.Lock()
38
+
39
+
40
+ def _hash_access_code(code: str, salt: str) -> str:
41
+ payload = f"{salt}:{code}".encode()
42
+ return hashlib.sha256(payload).hexdigest()
43
+
44
+
45
+ def generate_access_code() -> str:
46
+ groups = [
47
+ "".join(secrets.choice(ACCESS_CODE_ALPHABET) for _ in range(4))
48
+ for _ in range(3)
49
+ ]
50
+ return "-".join(groups)
51
+
52
+
53
+ def is_access_configured(access: AccessSettings) -> bool:
54
+ return bool(
55
+ access.code.strip() and access.code_hash.strip() and access.code_salt.strip()
56
+ )
57
+
58
+
59
+ def set_access_code(settings: Settings, code: str) -> None:
60
+ salt = secrets.token_hex(16)
61
+ current_generation = settings.access.session_generation
62
+ session_signing_secret = settings.access.session_signing_secret
63
+ settings.access = AccessSettings(
64
+ code=code,
65
+ code_hash=_hash_access_code(code, salt),
66
+ code_salt=salt,
67
+ session_generation=1 if current_generation <= 0 else current_generation + 1,
68
+ session_signing_secret=session_signing_secret,
69
+ )
70
+ with _runtime_lock:
71
+ _runtime_state.bootstrap_generated = False
72
+
73
+
74
+ def clear_access_code(settings: Settings) -> bool:
75
+ was_configured = is_access_configured(settings.access)
76
+ next_generation = (
77
+ 1
78
+ if settings.access.session_generation <= 0
79
+ else settings.access.session_generation + 1
80
+ )
81
+ settings.access = AccessSettings(
82
+ session_generation=next_generation,
83
+ session_signing_secret=settings.access.session_signing_secret,
84
+ )
85
+ with _runtime_lock:
86
+ _runtime_state.bootstrap_generated = False
87
+ return was_configured
88
+
89
+
90
+ def verify_access_code(access: AccessSettings, code: str) -> bool:
91
+ if not is_access_configured(access):
92
+ return False
93
+ expected = _hash_access_code(code, access.code_salt)
94
+ return secrets.compare_digest(expected, access.code_hash)
95
+
96
+
97
+ def ensure_access_bootstrap(settings: Settings) -> str | None:
98
+ generated_code: str | None = None
99
+ if not is_access_configured(settings.access):
100
+ generated_code = generate_access_code()
101
+ set_access_code(settings, generated_code)
102
+ logger.warning("Flowent admin access code: {}", settings.access.code)
103
+ with _runtime_lock:
104
+ _runtime_state.bootstrap_generated = generated_code is not None
105
+ return generated_code
106
+
107
+
108
+ def ensure_session_signing_secret(settings: Settings) -> bool:
109
+ if settings.access.session_signing_secret.strip():
110
+ return False
111
+ settings.access.session_signing_secret = secrets.token_urlsafe(32)
112
+ return True
113
+
114
+
115
+ def _read_live_access_settings() -> AccessSettings:
116
+ from flowent.settings import _SETTINGS_FILE, _read_settings_file
117
+
118
+ settings_file = Path(_SETTINGS_FILE)
119
+ if settings_file.exists():
120
+ try:
121
+ settings, _ = _read_settings_file()
122
+ return settings.access
123
+ except Exception as exc:
124
+ logger.warning(
125
+ "Failed to read live access settings from {}: {}",
126
+ settings_file,
127
+ exc,
128
+ )
129
+ return get_settings().access
130
+
131
+
132
+ def _build_live_signature(access: AccessSettings) -> tuple[bool, int, str]:
133
+ return (
134
+ is_access_configured(access),
135
+ access.session_generation,
136
+ access.session_signing_secret,
137
+ )
138
+
139
+
140
+ def initialize_live_access_signature() -> None:
141
+ access = _read_live_access_settings()
142
+ with _runtime_lock:
143
+ _runtime_state.live_signature = _build_live_signature(access)
144
+
145
+
146
+ def refresh_live_access_signature() -> bool:
147
+ access = _read_live_access_settings()
148
+ next_signature = _build_live_signature(access)
149
+ with _runtime_lock:
150
+ previous_signature = _runtime_state.live_signature
151
+ _runtime_state.live_signature = next_signature
152
+ return previous_signature is not None and previous_signature != next_signature
153
+
154
+
155
+ def is_authenticated_session(
156
+ session: Mapping[str, Any] | None,
157
+ access: AccessSettings | None = None,
158
+ ) -> bool:
159
+ if session is None:
160
+ return False
161
+ current_access = access or _read_live_access_settings()
162
+ if not is_access_configured(current_access):
163
+ return False
164
+ raw_generation = session.get(ACCESS_SESSION_KEY)
165
+ if isinstance(raw_generation, bool) or not isinstance(raw_generation, int):
166
+ return False
167
+ return raw_generation == current_access.session_generation
168
+
169
+
170
+ def build_access_state_payload(
171
+ session: Mapping[str, Any] | None,
172
+ ) -> dict[str, object]:
173
+ access = _read_live_access_settings()
174
+ with _runtime_lock:
175
+ bootstrap_generated = _runtime_state.bootstrap_generated
176
+ configured = is_access_configured(access)
177
+ return {
178
+ "authenticated": is_authenticated_session(session, access),
179
+ "configured": configured,
180
+ "bootstrap_generated": configured and bootstrap_generated,
181
+ "requires_restart": not configured,
182
+ }
183
+
184
+
185
+ def access_request_is_public(path: str) -> bool:
186
+ return path in ACCESS_PUBLIC_PATHS
187
+
188
+
189
+ class AccessControlMiddleware(BaseHTTPMiddleware):
190
+ async def dispatch(self, request: Request, call_next):
191
+ path = request.url.path
192
+ if not path.startswith("/api") or access_request_is_public(path):
193
+ return await call_next(request)
194
+ if is_authenticated_session(request.session):
195
+ return await call_next(request)
196
+ return JSONResponse({"detail": "Access denied"}, status_code=401)
197
+
198
+
199
+ async def authorize_websocket(ws: WebSocket) -> bool:
200
+ if is_authenticated_session(ws.scope.get("session")):
201
+ return True
202
+ await ws.close(code=4401, reason="Access denied")
203
+ return False
204
+
205
+
206
+ def reset_local_access() -> str:
207
+ from flowent.events import event_bus
208
+
209
+ settings = get_settings()
210
+ clear_access_code(settings)
211
+ save_settings(settings)
212
+ initialize_live_access_signature()
213
+ event_bus.close_all_connections(code=4001, reason="Access session reset")
214
+ return (
215
+ "Access configuration cleared. Restart Flowent to generate a new access code."
216
+ )
217
+
218
+
219
+ def refresh_local_access() -> str:
220
+ from flowent.events import event_bus
221
+
222
+ settings = get_settings()
223
+ next_code = generate_access_code()
224
+ set_access_code(settings, next_code)
225
+ save_settings(settings)
226
+ initialize_live_access_signature()
227
+ event_bus.close_all_connections(code=4001, reason="Access session refreshed")
228
+ return f"Generated new access code: {next_code}"
229
+
230
+
231
+ __all__ = [
232
+ "ACCESS_SESSION_KEY",
233
+ "AccessControlMiddleware",
234
+ "authorize_websocket",
235
+ "build_access_state_payload",
236
+ "clear_access_code",
237
+ "ensure_access_bootstrap",
238
+ "ensure_session_signing_secret",
239
+ "initialize_live_access_signature",
240
+ "is_access_configured",
241
+ "is_authenticated_session",
242
+ "refresh_live_access_signature",
243
+ "refresh_local_access",
244
+ "reset_local_access",
245
+ "set_access_code",
246
+ "verify_access_code",
247
+ ]