flock-core 0.5.0b27__py3-none-any.whl → 0.5.0b50__py3-none-any.whl

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.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (357) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/{core/util → helper}/cli_helper.py +4 -3
  17. flock/{core/logging → logging}/__init__.py +2 -3
  18. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  19. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  20. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  21. flock/{core/logging → logging}/logging.py +77 -61
  22. flock/{core/logging → logging}/telemetry.py +20 -26
  23. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  24. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  25. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  26. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  27. flock/mcp/__init__.py +91 -0
  28. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  29. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  30. flock/mcp/manager.py +255 -0
  31. flock/mcp/servers/sse/__init__.py +1 -1
  32. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  33. flock/mcp/servers/stdio/__init__.py +1 -1
  34. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  35. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  36. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  37. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  38. flock/mcp/types/__init__.py +42 -0
  39. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  40. flock/{core/mcp → mcp}/types/factories.py +7 -6
  41. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  42. flock/{core/mcp → mcp}/types/types.py +70 -74
  43. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  44. flock/orchestrator.py +645 -0
  45. flock/registry.py +148 -0
  46. flock/runtime.py +262 -0
  47. flock/service.py +140 -0
  48. flock/store.py +69 -0
  49. flock/subscription.py +111 -0
  50. flock/themes/andromeda.toml +1 -1
  51. flock/themes/apple-system-colors.toml +1 -1
  52. flock/themes/arcoiris.toml +1 -1
  53. flock/themes/atomonelight.toml +1 -1
  54. flock/themes/ayu copy.toml +1 -1
  55. flock/themes/ayu-light.toml +1 -1
  56. flock/themes/belafonte-day.toml +1 -1
  57. flock/themes/belafonte-night.toml +1 -1
  58. flock/themes/blulocodark.toml +1 -1
  59. flock/themes/breeze.toml +1 -1
  60. flock/themes/broadcast.toml +1 -1
  61. flock/themes/brogrammer.toml +1 -1
  62. flock/themes/builtin-dark.toml +1 -1
  63. flock/themes/builtin-pastel-dark.toml +1 -1
  64. flock/themes/catppuccin-latte.toml +1 -1
  65. flock/themes/catppuccin-macchiato.toml +1 -1
  66. flock/themes/catppuccin-mocha.toml +1 -1
  67. flock/themes/cga.toml +1 -1
  68. flock/themes/chalk.toml +1 -1
  69. flock/themes/ciapre.toml +1 -1
  70. flock/themes/coffee-theme.toml +1 -1
  71. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  72. flock/themes/dark+.toml +1 -1
  73. flock/themes/darkermatrix.toml +1 -1
  74. flock/themes/darkside.toml +1 -1
  75. flock/themes/desert.toml +1 -1
  76. flock/themes/django.toml +1 -1
  77. flock/themes/djangosmooth.toml +1 -1
  78. flock/themes/doomone.toml +1 -1
  79. flock/themes/dotgov.toml +1 -1
  80. flock/themes/dracula+.toml +1 -1
  81. flock/themes/duckbones.toml +1 -1
  82. flock/themes/encom.toml +1 -1
  83. flock/themes/espresso.toml +1 -1
  84. flock/themes/everblush.toml +1 -1
  85. flock/themes/fairyfloss.toml +1 -1
  86. flock/themes/fideloper.toml +1 -1
  87. flock/themes/fishtank.toml +1 -1
  88. flock/themes/flexoki-light.toml +1 -1
  89. flock/themes/floraverse.toml +1 -1
  90. flock/themes/framer.toml +1 -1
  91. flock/themes/galizur.toml +1 -1
  92. flock/themes/github.toml +1 -1
  93. flock/themes/grass.toml +1 -1
  94. flock/themes/grey-green.toml +1 -1
  95. flock/themes/gruvboxlight.toml +1 -1
  96. flock/themes/guezwhoz.toml +1 -1
  97. flock/themes/harper.toml +1 -1
  98. flock/themes/hax0r-blue.toml +1 -1
  99. flock/themes/hopscotch.256.toml +1 -1
  100. flock/themes/ic-green-ppl.toml +1 -1
  101. flock/themes/iceberg-dark.toml +1 -1
  102. flock/themes/japanesque.toml +1 -1
  103. flock/themes/jubi.toml +1 -1
  104. flock/themes/kibble.toml +1 -1
  105. flock/themes/kolorit.toml +1 -1
  106. flock/themes/kurokula.toml +1 -1
  107. flock/themes/materialdesigncolors.toml +1 -1
  108. flock/themes/matrix.toml +1 -1
  109. flock/themes/mellifluous.toml +1 -1
  110. flock/themes/midnight-in-mojave.toml +1 -1
  111. flock/themes/monokai-remastered.toml +1 -1
  112. flock/themes/monokai-soda.toml +1 -1
  113. flock/themes/neon.toml +1 -1
  114. flock/themes/neopolitan.toml +1 -1
  115. flock/themes/nord-light.toml +1 -1
  116. flock/themes/ocean.toml +1 -1
  117. flock/themes/onehalfdark.toml +1 -1
  118. flock/themes/onehalflight.toml +1 -1
  119. flock/themes/palenighthc.toml +1 -1
  120. flock/themes/paulmillr.toml +1 -1
  121. flock/themes/pencildark.toml +1 -1
  122. flock/themes/pnevma.toml +1 -1
  123. flock/themes/purple-rain.toml +1 -1
  124. flock/themes/purplepeter.toml +1 -1
  125. flock/themes/raycast-dark.toml +1 -1
  126. flock/themes/red-sands.toml +1 -1
  127. flock/themes/relaxed.toml +1 -1
  128. flock/themes/retro.toml +1 -1
  129. flock/themes/rose-pine.toml +1 -1
  130. flock/themes/royal.toml +1 -1
  131. flock/themes/ryuuko.toml +1 -1
  132. flock/themes/sakura.toml +1 -1
  133. flock/themes/scarlet-protocol.toml +1 -1
  134. flock/themes/seoulbones-dark.toml +1 -1
  135. flock/themes/shades-of-purple.toml +1 -1
  136. flock/themes/smyck.toml +1 -1
  137. flock/themes/softserver.toml +1 -1
  138. flock/themes/solarized-darcula.toml +1 -1
  139. flock/themes/square.toml +1 -1
  140. flock/themes/sugarplum.toml +1 -1
  141. flock/themes/thayer-bright.toml +1 -1
  142. flock/themes/tokyonight.toml +1 -1
  143. flock/themes/tomorrow.toml +1 -1
  144. flock/themes/ubuntu.toml +1 -1
  145. flock/themes/ultradark.toml +1 -1
  146. flock/themes/ultraviolent.toml +1 -1
  147. flock/themes/unikitty.toml +1 -1
  148. flock/themes/urple.toml +1 -1
  149. flock/themes/vesper.toml +1 -1
  150. flock/themes/vimbones.toml +1 -1
  151. flock/themes/wildcherry.toml +1 -1
  152. flock/themes/wilmersdorf.toml +1 -1
  153. flock/themes/wryan.toml +1 -1
  154. flock/themes/xcodedarkhc.toml +1 -1
  155. flock/themes/xcodelight.toml +1 -1
  156. flock/themes/zenbones-light.toml +1 -1
  157. flock/themes/zenwritten-dark.toml +1 -1
  158. flock/utilities.py +301 -0
  159. flock/{components/utility → utility}/output_utility_component.py +68 -53
  160. flock/visibility.py +107 -0
  161. flock_core-0.5.0b50.dist-info/METADATA +747 -0
  162. flock_core-0.5.0b50.dist-info/RECORD +398 -0
  163. flock_core-0.5.0b50.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.dist-info}/licenses/LICENSE +1 -1
  165. flock/adapter/__init__.py +0 -14
  166. flock/adapter/azure_adapter.py +0 -68
  167. flock/adapter/chroma_adapter.py +0 -73
  168. flock/adapter/faiss_adapter.py +0 -97
  169. flock/adapter/pinecone_adapter.py +0 -51
  170. flock/adapter/vector_base.py +0 -47
  171. flock/cli/assets/release_notes.md +0 -140
  172. flock/cli/config.py +0 -8
  173. flock/cli/constants.py +0 -36
  174. flock/cli/create_agent.py +0 -1
  175. flock/cli/create_flock.py +0 -280
  176. flock/cli/execute_flock.py +0 -620
  177. flock/cli/load_agent.py +0 -1
  178. flock/cli/load_examples.py +0 -1
  179. flock/cli/load_flock.py +0 -192
  180. flock/cli/load_release_notes.py +0 -20
  181. flock/cli/loaded_flock_cli.py +0 -254
  182. flock/cli/manage_agents.py +0 -459
  183. flock/cli/registry_management.py +0 -889
  184. flock/cli/runner.py +0 -41
  185. flock/cli/settings.py +0 -857
  186. flock/cli/utils.py +0 -135
  187. flock/cli/view_results.py +0 -29
  188. flock/cli/yaml_editor.py +0 -396
  189. flock/components/__init__.py +0 -30
  190. flock/components/evaluation/__init__.py +0 -9
  191. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  192. flock/components/routing/__init__.py +0 -15
  193. flock/components/routing/conditional_routing_component.py +0 -494
  194. flock/components/routing/default_routing_component.py +0 -103
  195. flock/components/routing/llm_routing_component.py +0 -206
  196. flock/components/utility/__init__.py +0 -15
  197. flock/components/utility/memory_utility_component.py +0 -550
  198. flock/components/utility/metrics_utility_component.py +0 -700
  199. flock/config.py +0 -61
  200. flock/core/__init__.py +0 -110
  201. flock/core/agent/__init__.py +0 -16
  202. flock/core/agent/default_agent.py +0 -180
  203. flock/core/agent/flock_agent_components.py +0 -104
  204. flock/core/agent/flock_agent_execution.py +0 -101
  205. flock/core/agent/flock_agent_integration.py +0 -260
  206. flock/core/agent/flock_agent_lifecycle.py +0 -186
  207. flock/core/agent/flock_agent_serialization.py +0 -381
  208. flock/core/api/__init__.py +0 -10
  209. flock/core/api/custom_endpoint.py +0 -45
  210. flock/core/api/endpoints.py +0 -254
  211. flock/core/api/main.py +0 -162
  212. flock/core/api/models.py +0 -97
  213. flock/core/api/run_store.py +0 -224
  214. flock/core/api/runner.py +0 -44
  215. flock/core/api/service.py +0 -214
  216. flock/core/component/__init__.py +0 -15
  217. flock/core/component/agent_component_base.py +0 -309
  218. flock/core/component/evaluation_component.py +0 -62
  219. flock/core/component/routing_component.py +0 -74
  220. flock/core/component/utility_component.py +0 -69
  221. flock/core/config/flock_agent_config.py +0 -58
  222. flock/core/config/scheduled_agent_config.py +0 -40
  223. flock/core/context/context.py +0 -213
  224. flock/core/context/context_manager.py +0 -37
  225. flock/core/context/context_vars.py +0 -10
  226. flock/core/evaluation/utils.py +0 -396
  227. flock/core/execution/batch_executor.py +0 -369
  228. flock/core/execution/evaluation_executor.py +0 -438
  229. flock/core/execution/local_executor.py +0 -31
  230. flock/core/execution/opik_executor.py +0 -103
  231. flock/core/execution/temporal_executor.py +0 -164
  232. flock/core/flock.py +0 -634
  233. flock/core/flock_agent.py +0 -336
  234. flock/core/flock_factory.py +0 -551
  235. flock/core/flock_scheduler.py +0 -166
  236. flock/core/flock_server_manager.py +0 -136
  237. flock/core/interpreter/python_interpreter.py +0 -689
  238. flock/core/mcp/__init__.py +0 -1
  239. flock/core/mcp/flock_mcp_server.py +0 -680
  240. flock/core/mcp/mcp_client_manager.py +0 -201
  241. flock/core/mcp/types/__init__.py +0 -1
  242. flock/core/mixin/dspy_integration.py +0 -403
  243. flock/core/mixin/prompt_parser.py +0 -125
  244. flock/core/orchestration/__init__.py +0 -15
  245. flock/core/orchestration/flock_batch_processor.py +0 -94
  246. flock/core/orchestration/flock_evaluator.py +0 -113
  247. flock/core/orchestration/flock_execution.py +0 -295
  248. flock/core/orchestration/flock_initialization.py +0 -149
  249. flock/core/orchestration/flock_server_manager.py +0 -67
  250. flock/core/orchestration/flock_web_server.py +0 -117
  251. flock/core/registry/__init__.py +0 -45
  252. flock/core/registry/agent_registry.py +0 -69
  253. flock/core/registry/callable_registry.py +0 -139
  254. flock/core/registry/component_discovery.py +0 -142
  255. flock/core/registry/component_registry.py +0 -64
  256. flock/core/registry/config_mapping.py +0 -64
  257. flock/core/registry/decorators.py +0 -137
  258. flock/core/registry/registry_hub.py +0 -205
  259. flock/core/registry/server_registry.py +0 -57
  260. flock/core/registry/type_registry.py +0 -86
  261. flock/core/serialization/__init__.py +0 -13
  262. flock/core/serialization/callable_registry.py +0 -52
  263. flock/core/serialization/flock_serializer.py +0 -832
  264. flock/core/serialization/json_encoder.py +0 -41
  265. flock/core/serialization/secure_serializer.py +0 -175
  266. flock/core/serialization/serializable.py +0 -342
  267. flock/core/serialization/serialization_utils.py +0 -412
  268. flock/core/util/file_path_utils.py +0 -223
  269. flock/core/util/hydrator.py +0 -309
  270. flock/core/util/input_resolver.py +0 -164
  271. flock/core/util/loader.py +0 -59
  272. flock/core/util/splitter.py +0 -219
  273. flock/di.py +0 -27
  274. flock/platform/docker_tools.py +0 -49
  275. flock/platform/jaeger_install.py +0 -86
  276. flock/webapp/__init__.py +0 -1
  277. flock/webapp/app/__init__.py +0 -0
  278. flock/webapp/app/api/__init__.py +0 -0
  279. flock/webapp/app/api/agent_management.py +0 -241
  280. flock/webapp/app/api/execution.py +0 -709
  281. flock/webapp/app/api/flock_management.py +0 -129
  282. flock/webapp/app/api/registry_viewer.py +0 -30
  283. flock/webapp/app/chat.py +0 -665
  284. flock/webapp/app/config.py +0 -104
  285. flock/webapp/app/dependencies.py +0 -117
  286. flock/webapp/app/main.py +0 -1070
  287. flock/webapp/app/middleware.py +0 -113
  288. flock/webapp/app/models_ui.py +0 -7
  289. flock/webapp/app/services/__init__.py +0 -0
  290. flock/webapp/app/services/feedback_file_service.py +0 -363
  291. flock/webapp/app/services/flock_service.py +0 -337
  292. flock/webapp/app/services/sharing_models.py +0 -81
  293. flock/webapp/app/services/sharing_store.py +0 -598
  294. flock/webapp/app/templates/theme_mapper.html +0 -326
  295. flock/webapp/app/theme_mapper.py +0 -812
  296. flock/webapp/app/utils.py +0 -85
  297. flock/webapp/run.py +0 -215
  298. flock/webapp/static/css/chat.css +0 -301
  299. flock/webapp/static/css/components.css +0 -167
  300. flock/webapp/static/css/header.css +0 -39
  301. flock/webapp/static/css/layout.css +0 -46
  302. flock/webapp/static/css/sidebar.css +0 -127
  303. flock/webapp/static/css/two-pane.css +0 -48
  304. flock/webapp/templates/base.html +0 -200
  305. flock/webapp/templates/chat.html +0 -152
  306. flock/webapp/templates/chat_settings.html +0 -19
  307. flock/webapp/templates/flock_editor.html +0 -16
  308. flock/webapp/templates/index.html +0 -12
  309. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  310. flock/webapp/templates/partials/_agent_list.html +0 -18
  311. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  312. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  313. flock/webapp/templates/partials/_chat_container.html +0 -15
  314. flock/webapp/templates/partials/_chat_messages.html +0 -57
  315. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  316. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  317. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  318. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  319. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  320. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  321. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  322. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  323. flock/webapp/templates/partials/_execution_form.html +0 -118
  324. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  325. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  326. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  327. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  328. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  329. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  330. flock/webapp/templates/partials/_registry_table.html +0 -25
  331. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  332. flock/webapp/templates/partials/_results_display.html +0 -78
  333. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  334. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  335. flock/webapp/templates/partials/_settings_view.html +0 -36
  336. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  337. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  338. flock/webapp/templates/partials/_sidebar.html +0 -74
  339. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  340. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  341. flock/webapp/templates/partials/_theme_preview.html +0 -36
  342. flock/webapp/templates/registry_viewer.html +0 -84
  343. flock/webapp/templates/shared_run_page.html +0 -140
  344. flock/workflow/__init__.py +0 -0
  345. flock/workflow/activities.py +0 -196
  346. flock/workflow/agent_activities.py +0 -24
  347. flock/workflow/agent_execution_activity.py +0 -202
  348. flock/workflow/flock_workflow.py +0 -214
  349. flock/workflow/temporal_config.py +0 -96
  350. flock/workflow/temporal_setup.py +0 -68
  351. flock_core-0.5.0b27.dist-info/METADATA +0 -274
  352. flock_core-0.5.0b27.dist-info/RECORD +0 -559
  353. flock_core-0.5.0b27.dist-info/entry_points.txt +0 -2
  354. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  355. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  356. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  357. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.dist-info}/WHEEL +0 -0
@@ -1,598 +0,0 @@
1
- """Shared link and feedback storage implementations supporting SQLite and Azure Table Storage."""
2
-
3
- import logging
4
- import sqlite3
5
- from abc import ABC, abstractmethod
6
- from pathlib import Path
7
- from typing import Any
8
-
9
- import aiosqlite
10
-
11
- from flock.webapp.app.services.sharing_models import (
12
- FeedbackRecord,
13
- SharedLinkConfig,
14
- )
15
-
16
- # Azure Table Storage imports - will be conditionally imported
17
- try:
18
- from azure.core.exceptions import ResourceExistsError, ResourceNotFoundError
19
- from azure.data.tables.aio import TableServiceClient
20
- AZURE_AVAILABLE = True
21
- except ImportError:
22
- AZURE_AVAILABLE = False
23
- TableServiceClient = None
24
- ResourceNotFoundError = None
25
- ResourceExistsError = None
26
-
27
- # Get a logger instance
28
- logger = logging.getLogger(__name__)
29
-
30
- class SharedLinkStoreInterface(ABC):
31
- """Interface for storing and retrieving shared link configurations."""
32
-
33
- @abstractmethod
34
- async def initialize(self) -> None:
35
- """Initialize the store (e.g., create tables)."""
36
- pass
37
-
38
- @abstractmethod
39
- async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
40
- """Saves a shared link configuration."""
41
- pass
42
-
43
- @abstractmethod
44
- async def get_config(self, share_id: str) -> SharedLinkConfig | None:
45
- """Retrieves a shared link configuration by its ID."""
46
- pass
47
-
48
- @abstractmethod
49
- async def delete_config(self, share_id: str) -> bool:
50
- """Deletes a shared link configuration by its ID. Returns True if deleted, False otherwise."""
51
- pass
52
-
53
- # Feedback
54
- @abstractmethod
55
- async def save_feedback(self, record: FeedbackRecord):
56
- """Persist a feedback record."""
57
- pass
58
-
59
- @abstractmethod
60
- async def get_feedback(self, id: str) -> FeedbackRecord | None:
61
- """Get a single feedback record."""
62
- pass
63
-
64
- @abstractmethod
65
- async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
66
- """Get all feedback records for a given agent."""
67
- pass
68
-
69
- class SQLiteSharedLinkStore(SharedLinkStoreInterface):
70
- """SQLite implementation for storing and retrieving shared link configurations."""
71
-
72
- def __init__(self, db_path: str):
73
- """Initialize SQLite store with database path."""
74
- self.db_path = Path(db_path)
75
- self.db_path.parent.mkdir(parents=True, exist_ok=True) # Ensure directory exists
76
- logger.info(f"SQLiteSharedLinkStore initialized with db_path: {self.db_path}")
77
-
78
- async def initialize(self) -> None:
79
- """Initializes the database and creates/updates the table if it doesn't exist."""
80
- try:
81
- async with aiosqlite.connect(self.db_path) as db:
82
- # Ensure the table exists with the base schema first
83
- await db.execute(
84
- """
85
- CREATE TABLE IF NOT EXISTS shared_links (
86
- share_id TEXT PRIMARY KEY,
87
- agent_name TEXT NOT NULL,
88
- flock_definition TEXT NOT NULL,
89
- created_at TEXT NOT NULL
90
- /* New columns will be added below if they don't exist */
91
- )
92
- """
93
- )
94
-
95
- # Add new columns individually, ignoring errors if they already exist
96
- new_columns = [
97
- ("share_type", "TEXT DEFAULT 'agent_run' NOT NULL"),
98
- ("chat_message_key", "TEXT"),
99
- ("chat_history_key", "TEXT"),
100
- ("chat_response_key", "TEXT")
101
- ]
102
-
103
- for column_name, column_type in new_columns:
104
- try:
105
- await db.execute(f"ALTER TABLE shared_links ADD COLUMN {column_name} {column_type}")
106
- logger.info(f"Added column '{column_name}' to shared_links table.")
107
- except sqlite3.OperationalError as e:
108
- if "duplicate column name" in str(e).lower():
109
- logger.debug(f"Column '{column_name}' already exists in shared_links table.")
110
- else:
111
- raise # Re-raise if it's a different operational error
112
-
113
- # Feedback table
114
- await db.execute(
115
- """
116
- CREATE TABLE IF NOT EXISTS feedback (
117
- feedback_id TEXT PRIMARY KEY,
118
- share_id TEXT,
119
- context_type TEXT NOT NULL,
120
- reason TEXT NOT NULL,
121
- expected_response TEXT,
122
- actual_response TEXT,
123
- flock_name TEXT,
124
- agent_name TEXT,
125
- flock_definition TEXT,
126
- created_at TEXT NOT NULL,
127
- FOREIGN KEY(share_id) REFERENCES shared_links(share_id)
128
- )
129
- """
130
- )
131
-
132
- await db.commit()
133
- logger.info(f"Database initialized and shared_links table schema ensured at {self.db_path}")
134
- except sqlite3.Error as e:
135
- logger.error(f"SQLite error during initialization: {e}", exc_info=True)
136
- raise
137
-
138
- async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
139
- """Saves a shared link configuration to the SQLite database."""
140
- try:
141
- async with aiosqlite.connect(self.db_path) as db:
142
- await db.execute(
143
- """INSERT INTO shared_links (
144
- share_id, agent_name, created_at, flock_definition,
145
- share_type, chat_message_key, chat_history_key, chat_response_key
146
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
147
- (
148
- config.share_id,
149
- config.agent_name,
150
- config.created_at.isoformat(),
151
- config.flock_definition,
152
- config.share_type,
153
- config.chat_message_key,
154
- config.chat_history_key,
155
- config.chat_response_key,
156
- ),
157
- )
158
- await db.commit()
159
- logger.info(f"Saved shared link config for ID: {config.share_id} with type: {config.share_type}")
160
- return config
161
- except sqlite3.Error as e:
162
- logger.error(f"SQLite error saving config for ID {config.share_id}: {e}", exc_info=True)
163
- raise
164
-
165
- async def get_config(self, share_id: str) -> SharedLinkConfig | None:
166
- """Retrieves a shared link configuration from SQLite by its ID."""
167
- try:
168
- async with aiosqlite.connect(self.db_path) as db:
169
- async with db.execute(
170
- """SELECT
171
- share_id, agent_name, created_at, flock_definition,
172
- share_type, chat_message_key, chat_history_key, chat_response_key
173
- FROM shared_links WHERE share_id = ?""",
174
- (share_id,)
175
- ) as cursor:
176
- row = await cursor.fetchone()
177
- if row:
178
- logger.debug(f"Retrieved shared link config for ID: {share_id}")
179
- return SharedLinkConfig(
180
- share_id=row[0],
181
- agent_name=row[1],
182
- created_at=row[2], # SQLite stores as TEXT, Pydantic will parse from ISO format
183
- flock_definition=row[3],
184
- share_type=row[4],
185
- chat_message_key=row[5],
186
- chat_history_key=row[6],
187
- chat_response_key=row[7],
188
- )
189
- logger.debug(f"No shared link config found for ID: {share_id}")
190
- return None
191
- except sqlite3.Error as e:
192
- logger.error(f"SQLite error retrieving config for ID {share_id}: {e}", exc_info=True)
193
- return None # Or raise, depending on desired error handling
194
-
195
- async def delete_config(self, share_id: str) -> bool:
196
- """Deletes a shared link configuration from SQLite by its ID."""
197
- try:
198
- async with aiosqlite.connect(self.db_path) as db:
199
- result = await db.execute("DELETE FROM shared_links WHERE share_id = ?", (share_id,))
200
- await db.commit()
201
- deleted_count = result.rowcount
202
- if deleted_count > 0:
203
- logger.info(f"Deleted shared link config for ID: {share_id}")
204
- return True
205
- logger.info(f"Attempted to delete non-existent shared link config for ID: {share_id}")
206
- return False
207
- except sqlite3.Error as e:
208
- logger.error(f"SQLite error deleting config for ID {share_id}: {e}", exc_info=True)
209
- return False # Or raise
210
-
211
- # ----------------------- Feedback methods -----------------------
212
- async def get_feedback(self, id: str) -> FeedbackRecord | None:
213
- """Retrieve a single feedback record from SQLite."""
214
- try:
215
- async with aiosqlite.connect(self.db_path) as db, db.execute(
216
- """SELECT
217
- feedback_id, share_id, context_type, reason,
218
- expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
219
- FROM feedback WHERE feedback_id = ?""",
220
- (id,)
221
- ) as cursor:
222
- row = await cursor.fetchone()
223
-
224
- if row:
225
- logger.debug(f"Retrieved feedback record for ID: {id}")
226
- return FeedbackRecord(
227
- feedback_id=row[0],
228
- share_id=row[1],
229
- context_type=row[2],
230
- reason=row[3],
231
- expected_response=row[4],
232
- actual_response=row[5],
233
- flock_name=row[6],
234
- agent_name=row[7],
235
- flock_definition=row[8],
236
- created_at=row[9], # SQLite stores as TEXT, Pydantic will parse from ISO format
237
- )
238
-
239
- logger.debug(f"No feedback record found for ID: {id}")
240
- return None
241
- except sqlite3.Error as e:
242
- logger.error(f"SQLite error retrieving feedback for ID {id}: {e}", exc_info=True)
243
- return None # Or raise, depending on desired error handling
244
-
245
- async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
246
- """Retrieve all feedback records from SQLite."""
247
- try:
248
- async with aiosqlite.connect(self.db_path) as db, db.execute(
249
- """SELECT
250
- feedback_id, share_id, context_type, reason,
251
- expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
252
- FROM feedback WHERE agent_name = ? ORDER BY created_at DESC""",
253
- (agent_name,)
254
- ) as cursor:
255
- rows = await cursor.fetchall()
256
-
257
- records = []
258
- for row in rows:
259
- records.append(FeedbackRecord(
260
- feedback_id=row[0],
261
- share_id=row[1],
262
- context_type=row[2],
263
- reason=row[3],
264
- expected_response=row[4],
265
- actual_response=row[5],
266
- flock_name=row[6],
267
- agent_name=row[7],
268
- flock_definition=row[8],
269
- created_at=row[9], # SQLite stores as TEXT, Pydantic will parse from ISO format
270
- ))
271
-
272
- logger.debug(f"Retrieved {len(records)} feedback records")
273
- return records
274
- except sqlite3.Error as e:
275
- logger.error(f"SQLite error retrieving all feedback records: {e}", exc_info=True)
276
- return [] # Return empty list on error
277
-
278
- async def save_feedback(self, record: FeedbackRecord) -> FeedbackRecord:
279
- """Persist a feedback record to SQLite."""
280
- try:
281
- async with aiosqlite.connect(self.db_path) as db:
282
- await db.execute(
283
- """INSERT INTO feedback (
284
- feedback_id, share_id, context_type, reason,
285
- expected_response, actual_response, flock_name, agent_name, flock_definition, created_at
286
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
287
- (
288
- record.feedback_id,
289
- record.share_id,
290
- record.context_type,
291
- record.reason,
292
- record.expected_response,
293
- record.actual_response,
294
- record.flock_name,
295
- record.agent_name,
296
- record.flock_definition,
297
- record.created_at.isoformat(),
298
- ),
299
- )
300
- await db.commit()
301
- logger.info(f"Saved feedback {record.feedback_id} (share={record.share_id})")
302
- return record
303
- except sqlite3.Error as e:
304
- logger.error(f"SQLite error saving feedback {record.feedback_id}: {e}", exc_info=True)
305
- raise
306
-
307
-
308
- # ---------------------------------------------------------------------------
309
- # Azure Table + Blob implementation
310
- # ---------------------------------------------------------------------------
311
-
312
- try:
313
- from azure.storage.blob.aio import BlobServiceClient
314
- AZURE_BLOB_AVAILABLE = True
315
- except ImportError: # blob SDK not installed
316
- AZURE_BLOB_AVAILABLE = False
317
- BlobServiceClient = None
318
-
319
- class AzureTableSharedLinkStore(SharedLinkStoreInterface):
320
- """Store configs in Azure Table; store large flock YAML in Blob Storage."""
321
-
322
- _TABLE_NAME = "flocksharedlinks"
323
- _FEEDBACK_TBL_NAME = "flockfeedback"
324
- _CONTAINER_NAME = "flocksharedlinkdefs" # blobs live here
325
- _PARTITION_KEY = "shared_links"
326
-
327
- def __init__(self, connection_string: str):
328
- if not AZURE_AVAILABLE:
329
- raise ImportError("pip install azure-data-tables")
330
- if not AZURE_BLOB_AVAILABLE:
331
- raise ImportError("pip install azure-storage-blob")
332
-
333
- self.connection_string = connection_string
334
- self.table_svc = TableServiceClient.from_connection_string(connection_string)
335
- self.blob_svc = BlobServiceClient.from_connection_string(connection_string)
336
-
337
- # ------------------------------------------------------------------ init
338
- async def initialize(self) -> None:
339
- # 1. Azure Tables ----------------------------------------------------
340
- try:
341
- await self.table_svc.create_table(self._TABLE_NAME)
342
- logger.info("Created Azure Table '%s'", self._TABLE_NAME)
343
- except ResourceExistsError:
344
- logger.debug("Azure Table '%s' already exists", self._TABLE_NAME)
345
-
346
- try:
347
- await self.table_svc.create_table(self._FEEDBACK_TBL_NAME)
348
- logger.info("Created Azure Table '%s'", self._FEEDBACK_TBL_NAME)
349
- except ResourceExistsError:
350
- logger.debug("Azure Table '%s' already exists", self._FEEDBACK_TBL_NAME)
351
-
352
- # 2. Blob container --------------------------------------------------
353
- try:
354
- await self.blob_svc.create_container(self._CONTAINER_NAME)
355
- logger.info("Created Blob container '%s'", self._CONTAINER_NAME)
356
- except ResourceExistsError:
357
- logger.debug("Blob container '%s' already exists", self._CONTAINER_NAME)
358
-
359
- # ------------------------------------------------------------- save_config
360
- async def save_config(self, config: SharedLinkConfig) -> SharedLinkConfig:
361
- """Upload YAML to Blob, then upsert table row containing the blob name."""
362
- blob_name = f"{config.share_id}.yaml"
363
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
364
-
365
- # 1. Upload flock_definition (overwrite in case of retry)
366
- await blob_client.upload_blob(config.flock_definition,
367
- overwrite=True,
368
- content_type="text/yaml")
369
- logger.debug("Uploaded blob '%s' (%d bytes)",
370
- blob_name, len(config.flock_definition.encode()))
371
-
372
- # 2. Persist lightweight record in the table
373
- tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
374
- entity = {
375
- "PartitionKey": self._PARTITION_KEY,
376
- "RowKey": config.share_id,
377
- "agent_name": config.agent_name,
378
- "created_at": config.created_at.isoformat(),
379
- "share_type": config.share_type,
380
- "chat_message_key": config.chat_message_key,
381
- "chat_history_key": config.chat_history_key,
382
- "chat_response_key": config.chat_response_key,
383
- # NEW – just a few bytes, well under 64 KiB
384
- "flock_blob_name": blob_name,
385
- }
386
- await tbl_client.upsert_entity(entity)
387
- logger.info("Saved shared link %s → blob '%s'", config.share_id, blob_name)
388
- return config
389
-
390
- # -------------------------------------------------------------- get_config
391
- async def get_config(self, share_id: str) -> SharedLinkConfig | None:
392
- tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
393
- try:
394
- entity = await tbl_client.get_entity(self._PARTITION_KEY, share_id)
395
- except ResourceNotFoundError:
396
- logger.debug("No config entity for id '%s'", share_id)
397
- return None
398
-
399
- blob_name = entity["flock_blob_name"]
400
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
401
- try:
402
- blob_bytes = await (await blob_client.download_blob()).readall()
403
- flock_yaml = blob_bytes.decode()
404
- except Exception as e:
405
- logger.error("Cannot download blob '%s' for share_id=%s: %s",
406
- blob_name, share_id, e, exc_info=True)
407
- raise
408
-
409
- return SharedLinkConfig(
410
- share_id = share_id,
411
- agent_name = entity["agent_name"],
412
- created_at = entity["created_at"],
413
- flock_definition = flock_yaml,
414
- share_type = entity.get("share_type", "agent_run"),
415
- chat_message_key = entity.get("chat_message_key"),
416
- chat_history_key = entity.get("chat_history_key"),
417
- chat_response_key = entity.get("chat_response_key"),
418
- )
419
-
420
- # ----------------------------------------------------------- delete_config
421
- async def delete_config(self, share_id: str) -> bool:
422
- tbl_client = self.table_svc.get_table_client(self._TABLE_NAME)
423
- try:
424
- entity = await tbl_client.get_entity(self._PARTITION_KEY, share_id)
425
- except ResourceNotFoundError:
426
- logger.info("Delete: entity %s not found", share_id)
427
- return False
428
-
429
- # 1. Remove blob (ignore missing blob)
430
- blob_name = entity["flock_blob_name"]
431
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
432
- try:
433
- await blob_client.delete_blob(delete_snapshots="include")
434
- logger.debug("Deleted blob '%s'", blob_name)
435
- except ResourceNotFoundError:
436
- logger.warning("Blob '%s' already gone", blob_name)
437
-
438
- # 2. Remove table row
439
- await tbl_client.delete_entity(self._PARTITION_KEY, share_id)
440
- logger.info("Deleted shared link %s and its blob", share_id)
441
- return True
442
-
443
- # -------------------------------------------------------- save_feedback --
444
- async def save_feedback(self, record: FeedbackRecord) -> FeedbackRecord:
445
- """Persist a feedback record. If a flock_definition is present, upload it as a blob and store only a reference in the table row to avoid oversized entities (64 KiB limit)."""
446
- tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
447
-
448
- # Core entity fields (avoid dumping the full Pydantic model – too many columns / large value)
449
- entity: dict[str, Any] = {
450
- "PartitionKey": "feedback",
451
- "RowKey": record.feedback_id,
452
- "share_id": record.share_id,
453
- "context_type": record.context_type,
454
- "reason": record.reason,
455
- "expected_response": record.expected_response,
456
- "actual_response": record.actual_response,
457
- "created_at": record.created_at.isoformat(),
458
- }
459
-
460
- # additional sanity check
461
-
462
- if record.flock_name is not None:
463
- entity["flock_name"] = record.flock_name
464
- if record.agent_name is not None:
465
- entity["agent_name"] = record.agent_name
466
-
467
- # ------------------------------------------------------------------ YAML → Blob
468
- if record.flock_definition:
469
- blob_name = f"{record.feedback_id}.yaml"
470
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
471
- # Overwrite=true so repeated feedback_id uploads (shouldn't happen) won't error
472
- await blob_client.upload_blob(record.flock_definition,
473
- overwrite=True,
474
- content_type="text/yaml")
475
- entity["flock_blob_name"] = blob_name # lightweight reference only
476
-
477
- # ------------------------------------------------------------------ Table upsert
478
- await tbl_client.upsert_entity(entity)
479
- logger.info("Saved feedback %s%s",
480
- record.feedback_id,
481
- f" → blob '{entity['flock_blob_name']}'" if "flock_blob_name" in entity else "")
482
- return record
483
-
484
- # -------------------------------------------------------- get_feedback --
485
- async def get_feedback(self, id: str) -> FeedbackRecord | None:
486
- """Retrieve a single feedback record from Azure Table Storage."""
487
- tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
488
- try:
489
- entity = await tbl_client.get_entity("feedback", id)
490
- except ResourceNotFoundError:
491
- logger.debug("No feedback record found for ID: %s", id)
492
- return None
493
-
494
- # Get flock_definition from blob if it exists
495
- flock_definition = None
496
- if "flock_blob_name" in entity:
497
- blob_name = entity["flock_blob_name"]
498
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
499
- try:
500
- blob_bytes = await (await blob_client.download_blob()).readall()
501
- flock_definition = blob_bytes.decode()
502
- except Exception as e:
503
- logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
504
- blob_name, id, e, exc_info=True)
505
- # Continue without flock_definition rather than failing
506
-
507
- return FeedbackRecord(
508
- feedback_id=id,
509
- share_id=entity.get("share_id"),
510
- context_type=entity["context_type"],
511
- reason=entity["reason"],
512
- expected_response=entity.get("expected_response"),
513
- actual_response=entity.get("actual_response"),
514
- flock_name=entity.get("flock_name"),
515
- agent_name=entity.get("agent_name"),
516
- flock_definition=flock_definition,
517
- created_at=entity["created_at"],
518
- )
519
-
520
-
521
- # ------------------------------------------------ get_all_feedback_records --
522
- async def get_all_feedback_records_for_agent(self, agent_name: str) -> list[FeedbackRecord]:
523
- """Retrieve all feedback records from Azure Table Storage for a specific agent."""
524
- tbl_client = self.table_svc.get_table_client(self._FEEDBACK_TBL_NAME)
525
-
526
- # Use Azure Table Storage filtering to only get records for the specified agent
527
- escaped_agent_name = agent_name.replace("'", "''")
528
- filter_query = f"agent_name eq '{escaped_agent_name}'"
529
-
530
- logger.debug(f"Querying feedback records with filter: {filter_query}")
531
-
532
- records = []
533
- try:
534
- async for entity in tbl_client.query_entities(filter_query):
535
- # Get flock_definition from blob if it exists
536
- flock_definition = None
537
- if "flock_blob_name" in entity:
538
- blob_name = entity["flock_blob_name"]
539
- blob_client = self.blob_svc.get_blob_client(self._CONTAINER_NAME, blob_name)
540
- try:
541
- blob_bytes = await (await blob_client.download_blob()).readall()
542
- flock_definition = blob_bytes.decode()
543
- except Exception as e:
544
- logger.error("Cannot download blob '%s' for feedback_id=%s: %s",
545
- blob_name, entity["RowKey"], e, exc_info=True)
546
- # Continue without flock_definition rather than failing
547
-
548
- records.append(FeedbackRecord(
549
- feedback_id=entity["RowKey"],
550
- share_id=entity.get("share_id"),
551
- context_type=entity["context_type"],
552
- reason=entity["reason"],
553
- expected_response=entity.get("expected_response"),
554
- actual_response=entity.get("actual_response"),
555
- flock_name=entity.get("flock_name"),
556
- agent_name=entity.get("agent_name"),
557
- flock_definition=flock_definition,
558
- created_at=entity["created_at"],
559
- ))
560
-
561
- logger.debug("Retrieved %d feedback records for agent %s", len(records), agent_name)
562
- return records
563
-
564
- except Exception as e:
565
- # Log the error.
566
- logger.error(
567
- f"Unable to query entries for agent {agent_name}. Exception: {e}"
568
- )
569
- return records
570
-
571
-
572
- # ----------------------- Factory Function -----------------------
573
-
574
- def create_shared_link_store(store_type: str | None = None, connection_string: str | None = None) -> SharedLinkStoreInterface:
575
- """Factory function to create the appropriate shared link store based on configuration.
576
-
577
- Args:
578
- store_type: Type of store to create ("local" for SQLite, "azure-storage" for Azure Table Storage)
579
- connection_string: Connection string for the store (file path for SQLite, connection string for Azure)
580
-
581
- Returns:
582
- Configured SharedLinkStoreInterface implementation
583
- """
584
- import os
585
-
586
- # Get values from environment if not provided
587
- if store_type is None:
588
- store_type = os.getenv("FLOCK_WEBAPP_STORE", "local").lower()
589
-
590
- if connection_string is None:
591
- connection_string = os.getenv("FLOCK_WEBAPP_STORE_CONNECTION", ".flock/shared_links.db")
592
-
593
- if store_type == "local":
594
- return SQLiteSharedLinkStore(connection_string)
595
- elif store_type == "azure-storage":
596
- return AzureTableSharedLinkStore(connection_string)
597
- else:
598
- raise ValueError(f"Unsupported store type: {store_type}. Supported types: 'local', 'azure-storage'")