hexdag 0.5.0.dev1__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.
Files changed (261) hide show
  1. hexdag/__init__.py +116 -0
  2. hexdag/__main__.py +30 -0
  3. hexdag/adapters/executors/__init__.py +5 -0
  4. hexdag/adapters/executors/local_executor.py +316 -0
  5. hexdag/builtin/__init__.py +6 -0
  6. hexdag/builtin/adapters/__init__.py +51 -0
  7. hexdag/builtin/adapters/anthropic/__init__.py +5 -0
  8. hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
  9. hexdag/builtin/adapters/database/__init__.py +6 -0
  10. hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
  11. hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
  12. hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
  13. hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
  14. hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
  15. hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
  16. hexdag/builtin/adapters/local/README.md +59 -0
  17. hexdag/builtin/adapters/local/__init__.py +7 -0
  18. hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
  19. hexdag/builtin/adapters/memory/__init__.py +47 -0
  20. hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
  21. hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
  22. hexdag/builtin/adapters/memory/schemas.py +57 -0
  23. hexdag/builtin/adapters/memory/session_memory.py +178 -0
  24. hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
  25. hexdag/builtin/adapters/memory/state_memory.py +280 -0
  26. hexdag/builtin/adapters/mock/README.md +89 -0
  27. hexdag/builtin/adapters/mock/__init__.py +15 -0
  28. hexdag/builtin/adapters/mock/hexdag.toml +50 -0
  29. hexdag/builtin/adapters/mock/mock_database.py +225 -0
  30. hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
  31. hexdag/builtin/adapters/mock/mock_llm.py +177 -0
  32. hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
  33. hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
  34. hexdag/builtin/adapters/openai/__init__.py +5 -0
  35. hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
  36. hexdag/builtin/adapters/secret/__init__.py +7 -0
  37. hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
  38. hexdag/builtin/adapters/unified_tool_router.py +280 -0
  39. hexdag/builtin/macros/__init__.py +17 -0
  40. hexdag/builtin/macros/conversation_agent.py +390 -0
  41. hexdag/builtin/macros/llm_macro.py +151 -0
  42. hexdag/builtin/macros/reasoning_agent.py +423 -0
  43. hexdag/builtin/macros/tool_macro.py +380 -0
  44. hexdag/builtin/nodes/__init__.py +38 -0
  45. hexdag/builtin/nodes/_discovery.py +123 -0
  46. hexdag/builtin/nodes/agent_node.py +696 -0
  47. hexdag/builtin/nodes/base_node_factory.py +242 -0
  48. hexdag/builtin/nodes/composite_node.py +926 -0
  49. hexdag/builtin/nodes/data_node.py +201 -0
  50. hexdag/builtin/nodes/expression_node.py +487 -0
  51. hexdag/builtin/nodes/function_node.py +454 -0
  52. hexdag/builtin/nodes/llm_node.py +491 -0
  53. hexdag/builtin/nodes/loop_node.py +920 -0
  54. hexdag/builtin/nodes/mapped_input.py +518 -0
  55. hexdag/builtin/nodes/port_call_node.py +269 -0
  56. hexdag/builtin/nodes/tool_call_node.py +195 -0
  57. hexdag/builtin/nodes/tool_utils.py +390 -0
  58. hexdag/builtin/prompts/__init__.py +68 -0
  59. hexdag/builtin/prompts/base.py +422 -0
  60. hexdag/builtin/prompts/chat_prompts.py +303 -0
  61. hexdag/builtin/prompts/error_correction_prompts.py +320 -0
  62. hexdag/builtin/prompts/tool_prompts.py +160 -0
  63. hexdag/builtin/tools/builtin_tools.py +84 -0
  64. hexdag/builtin/tools/database_tools.py +164 -0
  65. hexdag/cli/__init__.py +17 -0
  66. hexdag/cli/__main__.py +7 -0
  67. hexdag/cli/commands/__init__.py +27 -0
  68. hexdag/cli/commands/build_cmd.py +812 -0
  69. hexdag/cli/commands/create_cmd.py +208 -0
  70. hexdag/cli/commands/docs_cmd.py +293 -0
  71. hexdag/cli/commands/generate_types_cmd.py +252 -0
  72. hexdag/cli/commands/init_cmd.py +188 -0
  73. hexdag/cli/commands/pipeline_cmd.py +494 -0
  74. hexdag/cli/commands/plugin_dev_cmd.py +529 -0
  75. hexdag/cli/commands/plugins_cmd.py +441 -0
  76. hexdag/cli/commands/studio_cmd.py +101 -0
  77. hexdag/cli/commands/validate_cmd.py +221 -0
  78. hexdag/cli/main.py +84 -0
  79. hexdag/core/__init__.py +83 -0
  80. hexdag/core/config/__init__.py +20 -0
  81. hexdag/core/config/loader.py +479 -0
  82. hexdag/core/config/models.py +150 -0
  83. hexdag/core/configurable.py +294 -0
  84. hexdag/core/context/__init__.py +37 -0
  85. hexdag/core/context/execution_context.py +378 -0
  86. hexdag/core/docs/__init__.py +26 -0
  87. hexdag/core/docs/extractors.py +678 -0
  88. hexdag/core/docs/generators.py +890 -0
  89. hexdag/core/docs/models.py +120 -0
  90. hexdag/core/domain/__init__.py +10 -0
  91. hexdag/core/domain/dag.py +1225 -0
  92. hexdag/core/exceptions.py +234 -0
  93. hexdag/core/expression_parser.py +569 -0
  94. hexdag/core/logging.py +449 -0
  95. hexdag/core/models/__init__.py +17 -0
  96. hexdag/core/models/base.py +138 -0
  97. hexdag/core/orchestration/__init__.py +46 -0
  98. hexdag/core/orchestration/body_executor.py +481 -0
  99. hexdag/core/orchestration/components/__init__.py +97 -0
  100. hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
  101. hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
  102. hexdag/core/orchestration/components/execution_coordinator.py +360 -0
  103. hexdag/core/orchestration/components/health_check_manager.py +176 -0
  104. hexdag/core/orchestration/components/input_mapper.py +143 -0
  105. hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
  106. hexdag/core/orchestration/components/node_executor.py +377 -0
  107. hexdag/core/orchestration/components/secret_manager.py +202 -0
  108. hexdag/core/orchestration/components/wave_executor.py +158 -0
  109. hexdag/core/orchestration/constants.py +17 -0
  110. hexdag/core/orchestration/events/README.md +312 -0
  111. hexdag/core/orchestration/events/__init__.py +104 -0
  112. hexdag/core/orchestration/events/batching.py +330 -0
  113. hexdag/core/orchestration/events/decorators.py +139 -0
  114. hexdag/core/orchestration/events/events.py +573 -0
  115. hexdag/core/orchestration/events/observers/__init__.py +30 -0
  116. hexdag/core/orchestration/events/observers/core_observers.py +690 -0
  117. hexdag/core/orchestration/events/observers/models.py +111 -0
  118. hexdag/core/orchestration/events/taxonomy.py +269 -0
  119. hexdag/core/orchestration/hook_context.py +237 -0
  120. hexdag/core/orchestration/hooks.py +437 -0
  121. hexdag/core/orchestration/models.py +418 -0
  122. hexdag/core/orchestration/orchestrator.py +910 -0
  123. hexdag/core/orchestration/orchestrator_factory.py +275 -0
  124. hexdag/core/orchestration/port_wrappers.py +327 -0
  125. hexdag/core/orchestration/prompt/__init__.py +32 -0
  126. hexdag/core/orchestration/prompt/template.py +332 -0
  127. hexdag/core/pipeline_builder/__init__.py +21 -0
  128. hexdag/core/pipeline_builder/component_instantiator.py +386 -0
  129. hexdag/core/pipeline_builder/include_tag.py +265 -0
  130. hexdag/core/pipeline_builder/pipeline_config.py +133 -0
  131. hexdag/core/pipeline_builder/py_tag.py +223 -0
  132. hexdag/core/pipeline_builder/tag_discovery.py +268 -0
  133. hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
  134. hexdag/core/pipeline_builder/yaml_validator.py +569 -0
  135. hexdag/core/ports/__init__.py +65 -0
  136. hexdag/core/ports/api_call.py +133 -0
  137. hexdag/core/ports/database.py +489 -0
  138. hexdag/core/ports/embedding.py +215 -0
  139. hexdag/core/ports/executor.py +237 -0
  140. hexdag/core/ports/file_storage.py +117 -0
  141. hexdag/core/ports/healthcheck.py +87 -0
  142. hexdag/core/ports/llm.py +551 -0
  143. hexdag/core/ports/memory.py +70 -0
  144. hexdag/core/ports/observer_manager.py +130 -0
  145. hexdag/core/ports/secret.py +145 -0
  146. hexdag/core/ports/tool_router.py +94 -0
  147. hexdag/core/ports_builder.py +623 -0
  148. hexdag/core/protocols.py +273 -0
  149. hexdag/core/resolver.py +304 -0
  150. hexdag/core/schema/__init__.py +9 -0
  151. hexdag/core/schema/generator.py +742 -0
  152. hexdag/core/secrets.py +242 -0
  153. hexdag/core/types.py +413 -0
  154. hexdag/core/utils/async_warnings.py +206 -0
  155. hexdag/core/utils/schema_conversion.py +78 -0
  156. hexdag/core/utils/sql_validation.py +86 -0
  157. hexdag/core/validation/secure_json.py +148 -0
  158. hexdag/core/yaml_macro.py +517 -0
  159. hexdag/mcp_server.py +3120 -0
  160. hexdag/studio/__init__.py +10 -0
  161. hexdag/studio/build_ui.py +92 -0
  162. hexdag/studio/server/__init__.py +1 -0
  163. hexdag/studio/server/main.py +100 -0
  164. hexdag/studio/server/routes/__init__.py +9 -0
  165. hexdag/studio/server/routes/execute.py +208 -0
  166. hexdag/studio/server/routes/export.py +558 -0
  167. hexdag/studio/server/routes/files.py +207 -0
  168. hexdag/studio/server/routes/plugins.py +419 -0
  169. hexdag/studio/server/routes/validate.py +220 -0
  170. hexdag/studio/ui/index.html +13 -0
  171. hexdag/studio/ui/package-lock.json +2992 -0
  172. hexdag/studio/ui/package.json +31 -0
  173. hexdag/studio/ui/postcss.config.js +6 -0
  174. hexdag/studio/ui/public/hexdag.svg +5 -0
  175. hexdag/studio/ui/src/App.tsx +251 -0
  176. hexdag/studio/ui/src/components/Canvas.tsx +408 -0
  177. hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
  178. hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
  179. hexdag/studio/ui/src/components/Header.tsx +181 -0
  180. hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
  181. hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
  182. hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
  183. hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
  184. hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
  185. hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
  186. hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
  187. hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
  188. hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
  189. hexdag/studio/ui/src/components/index.ts +8 -0
  190. hexdag/studio/ui/src/index.css +92 -0
  191. hexdag/studio/ui/src/main.tsx +10 -0
  192. hexdag/studio/ui/src/types/index.ts +123 -0
  193. hexdag/studio/ui/src/vite-env.d.ts +1 -0
  194. hexdag/studio/ui/tailwind.config.js +29 -0
  195. hexdag/studio/ui/tsconfig.json +37 -0
  196. hexdag/studio/ui/tsconfig.node.json +13 -0
  197. hexdag/studio/ui/vite.config.ts +35 -0
  198. hexdag/visualization/__init__.py +69 -0
  199. hexdag/visualization/dag_visualizer.py +1020 -0
  200. hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
  201. hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
  202. hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
  203. hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
  204. hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
  205. hexdag_plugins/.gitignore +43 -0
  206. hexdag_plugins/README.md +73 -0
  207. hexdag_plugins/__init__.py +1 -0
  208. hexdag_plugins/azure/LICENSE +21 -0
  209. hexdag_plugins/azure/README.md +414 -0
  210. hexdag_plugins/azure/__init__.py +21 -0
  211. hexdag_plugins/azure/azure_blob_adapter.py +450 -0
  212. hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
  213. hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
  214. hexdag_plugins/azure/azure_openai_adapter.py +415 -0
  215. hexdag_plugins/azure/pyproject.toml +107 -0
  216. hexdag_plugins/azure/tests/__init__.py +1 -0
  217. hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
  218. hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
  219. hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
  220. hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
  221. hexdag_plugins/hexdag_etl/README.md +168 -0
  222. hexdag_plugins/hexdag_etl/__init__.py +53 -0
  223. hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
  224. hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
  225. hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
  226. hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
  227. hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
  228. hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
  229. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
  230. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
  231. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
  232. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
  233. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
  234. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
  235. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
  236. hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
  237. hexdag_plugins/hexdag_etl/test_transform.py +54 -0
  238. hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
  239. hexdag_plugins/mysql_adapter/LICENSE +21 -0
  240. hexdag_plugins/mysql_adapter/README.md +224 -0
  241. hexdag_plugins/mysql_adapter/__init__.py +6 -0
  242. hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
  243. hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
  244. hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
  245. hexdag_plugins/storage/README.md +184 -0
  246. hexdag_plugins/storage/__init__.py +19 -0
  247. hexdag_plugins/storage/file/__init__.py +5 -0
  248. hexdag_plugins/storage/file/local.py +325 -0
  249. hexdag_plugins/storage/ports/__init__.py +5 -0
  250. hexdag_plugins/storage/ports/vector_store.py +236 -0
  251. hexdag_plugins/storage/sql/__init__.py +7 -0
  252. hexdag_plugins/storage/sql/base.py +187 -0
  253. hexdag_plugins/storage/sql/mysql.py +27 -0
  254. hexdag_plugins/storage/sql/postgresql.py +27 -0
  255. hexdag_plugins/storage/tests/__init__.py +1 -0
  256. hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
  257. hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
  258. hexdag_plugins/storage/vector/__init__.py +7 -0
  259. hexdag_plugins/storage/vector/chromadb.py +223 -0
  260. hexdag_plugins/storage/vector/in_memory.py +285 -0
  261. hexdag_plugins/storage/vector/pgvector.py +502 -0
@@ -0,0 +1,10 @@
1
+ """hexdag studio - Local-first visual editor for hexdag pipelines.
2
+
3
+ Usage:
4
+ hexdag studio ./pipelines/
5
+
6
+ Opens a browser-based editor that reads/writes YAML files directly.
7
+ No cloud, no accounts - just local files that work with git.
8
+ """
9
+
10
+ __version__ = "0.1.0"
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python3
2
+ """Build script for hexdag studio UI.
3
+
4
+ This script builds the React UI and bundles it into the dist folder
5
+ which is served by the FastAPI server.
6
+
7
+ Usage:
8
+ python -m hexdag.studio.build_ui
9
+
10
+ Requirements:
11
+ - Node.js 18+
12
+ - npm or pnpm
13
+ """
14
+
15
+ import subprocess
16
+ import sys
17
+ from pathlib import Path
18
+
19
+
20
+ def build_ui() -> int:
21
+ """Build the React UI."""
22
+ ui_dir = Path(__file__).parent / "ui"
23
+
24
+ if not ui_dir.exists():
25
+ print(f"Error: UI directory not found: {ui_dir}")
26
+ return 1
27
+
28
+ print("Building hexdag studio UI...")
29
+ print(f" Directory: {ui_dir}")
30
+
31
+ # Check for package.json
32
+ package_json = ui_dir / "package.json"
33
+ if not package_json.exists():
34
+ print(f"Error: package.json not found: {package_json}")
35
+ return 1
36
+
37
+ # Try npm first, then pnpm
38
+ npm_cmd = "npm"
39
+ try:
40
+ subprocess.run([npm_cmd, "--version"], capture_output=True, check=True)
41
+ except (subprocess.CalledProcessError, FileNotFoundError):
42
+ npm_cmd = "pnpm"
43
+ try:
44
+ subprocess.run([npm_cmd, "--version"], capture_output=True, check=True)
45
+ except (subprocess.CalledProcessError, FileNotFoundError):
46
+ print("Error: Neither npm nor pnpm found. Please install Node.js.")
47
+ return 1
48
+
49
+ print(f" Using: {npm_cmd}")
50
+
51
+ # Install dependencies
52
+ print("\n[1/2] Installing dependencies...")
53
+ result = subprocess.run(
54
+ [npm_cmd, "install"],
55
+ cwd=ui_dir,
56
+ capture_output=False,
57
+ )
58
+ if result.returncode != 0:
59
+ print("Error: Failed to install dependencies")
60
+ return result.returncode
61
+
62
+ # Build
63
+ print("\n[2/2] Building production bundle...")
64
+ result = subprocess.run(
65
+ [npm_cmd, "run", "build"],
66
+ cwd=ui_dir,
67
+ capture_output=False,
68
+ )
69
+ if result.returncode != 0:
70
+ print("Error: Failed to build")
71
+ return result.returncode
72
+
73
+ dist_dir = ui_dir / "dist"
74
+ if dist_dir.exists():
75
+ print("\nBuild complete!")
76
+ print(f" Output: {dist_dir}")
77
+
78
+ # List built files
79
+ print("\nBuilt files:")
80
+ for f in dist_dir.rglob("*"):
81
+ if f.is_file():
82
+ size = f.stat().st_size
83
+ rel_path = f.relative_to(dist_dir)
84
+ print(f" {rel_path} ({size:,} bytes)")
85
+ else:
86
+ print("Warning: dist directory not created")
87
+
88
+ return 0
89
+
90
+
91
+ if __name__ == "__main__":
92
+ sys.exit(build_ui())
@@ -0,0 +1 @@
1
+ """FastAPI server for hexdag studio."""
@@ -0,0 +1,100 @@
1
+ """FastAPI server for hexdag studio.
2
+
3
+ Local-first visual editor for hexdag pipelines.
4
+ """
5
+
6
+ from collections.abc import AsyncGenerator
7
+ from contextlib import asynccontextmanager
8
+ from pathlib import Path
9
+
10
+ from fastapi import FastAPI
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from fastapi.responses import FileResponse
13
+ from fastapi.staticfiles import StaticFiles
14
+
15
+ from hexdag.studio.server.routes import (
16
+ execute_router,
17
+ export_router,
18
+ files_router,
19
+ plugins_router,
20
+ validate_router,
21
+ )
22
+ from hexdag.studio.server.routes.files import set_workspace_root
23
+
24
+
25
+ def create_app(workspace_path: Path) -> FastAPI:
26
+ """Create FastAPI application for hexdag studio.
27
+
28
+ Args:
29
+ workspace_path: Root directory for pipeline files
30
+ """
31
+
32
+ @asynccontextmanager
33
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
34
+ # Startup
35
+ set_workspace_root(workspace_path)
36
+ print(f"Workspace: {workspace_path}")
37
+ yield
38
+ # Shutdown
39
+ pass
40
+
41
+ app = FastAPI(
42
+ title="hexdag studio",
43
+ description="Local-first visual editor for hexdag pipelines",
44
+ version="0.1.0",
45
+ lifespan=lifespan,
46
+ )
47
+
48
+ # CORS for local development
49
+ app.add_middleware(
50
+ CORSMiddleware,
51
+ allow_origins=["http://localhost:3141", "http://127.0.0.1:3141", "http://localhost:5173"],
52
+ allow_credentials=True,
53
+ allow_methods=["*"],
54
+ allow_headers=["*"],
55
+ )
56
+
57
+ # API routes
58
+ app.include_router(files_router, prefix="/api")
59
+ app.include_router(validate_router, prefix="/api")
60
+ app.include_router(execute_router, prefix="/api")
61
+ app.include_router(export_router, prefix="/api")
62
+ app.include_router(plugins_router, prefix="/api")
63
+
64
+ # Health check
65
+ @app.get("/api/health")
66
+ async def health() -> dict[str, str]:
67
+ return {"status": "ok", "workspace": str(workspace_path)}
68
+
69
+ # Serve static UI files
70
+ ui_path = Path(__file__).parent.parent / "ui" / "dist"
71
+ if ui_path.exists():
72
+ app.mount("/assets", StaticFiles(directory=ui_path / "assets"), name="assets")
73
+
74
+ @app.get("/")
75
+ async def serve_index() -> FileResponse:
76
+ return FileResponse(ui_path / "index.html")
77
+
78
+ @app.get("/{path:path}")
79
+ async def serve_spa(path: str) -> FileResponse:
80
+ # SPA fallback - serve index.html for all non-API routes
81
+ file_path = ui_path / path
82
+ if file_path.exists() and file_path.is_file():
83
+ return FileResponse(file_path)
84
+ return FileResponse(ui_path / "index.html")
85
+
86
+ return app
87
+
88
+
89
+ def run_server(workspace_path: Path, host: str = "127.0.0.1", port: int = 3141) -> None:
90
+ """Run the studio server.
91
+
92
+ Args:
93
+ workspace_path: Root directory for pipeline files
94
+ host: Host to bind to
95
+ port: Port to bind to
96
+ """
97
+ import uvicorn
98
+
99
+ app = create_app(workspace_path)
100
+ uvicorn.run(app, host=host, port=port, log_level="info")
@@ -0,0 +1,9 @@
1
+ """API routes for hexdag studio."""
2
+
3
+ from hexdag.studio.server.routes.execute import router as execute_router
4
+ from hexdag.studio.server.routes.export import router as export_router
5
+ from hexdag.studio.server.routes.files import router as files_router
6
+ from hexdag.studio.server.routes.plugins import router as plugins_router
7
+ from hexdag.studio.server.routes.validate import router as validate_router
8
+
9
+ __all__ = ["files_router", "validate_router", "execute_router", "export_router", "plugins_router"]
@@ -0,0 +1,208 @@
1
+ """Execution API for hexdag studio.
2
+
3
+ Provides test execution of pipelines with mock adapters.
4
+ """
5
+
6
+ import asyncio
7
+ import sys
8
+ from typing import Any
9
+
10
+ from fastapi import APIRouter
11
+ from pydantic import BaseModel
12
+
13
+ from hexdag.studio.server.routes.files import get_workspace_root
14
+
15
+ router = APIRouter(prefix="/execute", tags=["execute"])
16
+
17
+
18
+ class ExecuteRequest(BaseModel):
19
+ """Request to execute a pipeline."""
20
+
21
+ content: str
22
+ inputs: dict[str, Any] = {}
23
+ use_mocks: bool = True
24
+ timeout: float = 30.0
25
+
26
+
27
+ class NodeResult(BaseModel):
28
+ """Result from a single node execution."""
29
+
30
+ name: str
31
+ status: str # completed, failed, skipped
32
+ output: Any | None = None
33
+ error: str | None = None
34
+ duration_ms: float | None = None
35
+
36
+
37
+ class ExecuteResponse(BaseModel):
38
+ """Pipeline execution result."""
39
+
40
+ success: bool
41
+ nodes: list[NodeResult]
42
+ final_output: Any | None = None
43
+ error: str | None = None
44
+ duration_ms: float
45
+
46
+
47
+ @router.post("", response_model=ExecuteResponse)
48
+ async def execute_pipeline(request: ExecuteRequest) -> ExecuteResponse:
49
+ """Execute a pipeline with optional mock adapters.
50
+
51
+ This is for testing/preview purposes. Production execution
52
+ should use the CLI or programmatic API.
53
+ """
54
+ import time
55
+
56
+ start = time.perf_counter()
57
+ node_results: list[NodeResult] = []
58
+
59
+ # Add workspace root to sys.path so local modules (like tools/) can be imported
60
+ try:
61
+ workspace_root = get_workspace_root()
62
+ # Check if workspace has a parent with tools/adapters (project root pattern)
63
+ # e.g., workspace is "examples/raven/pipelines", project root is "examples/raven"
64
+ project_root = (
65
+ workspace_root.parent if workspace_root.name == "pipelines" else workspace_root
66
+ )
67
+ project_root_str = str(project_root)
68
+ if project_root_str not in sys.path:
69
+ sys.path.insert(0, project_root_str)
70
+ except Exception:
71
+ pass # Workspace not set, continue without modification
72
+
73
+ try:
74
+ from hexdag.core.orchestration.orchestrator import Orchestrator
75
+ from hexdag.core.pipeline_builder import YamlPipelineBuilder
76
+
77
+ # Build pipeline
78
+ builder = YamlPipelineBuilder()
79
+ graph, config = builder.build_from_yaml_string(request.content)
80
+
81
+ # Create orchestrator with mocks if requested
82
+ if request.use_mocks:
83
+ # Use mock adapters for safe testing
84
+ from hexdag.builtin.adapters.memory import InMemoryMemory
85
+ from hexdag.builtin.adapters.mock import MockLLM
86
+
87
+ orchestrator = Orchestrator(
88
+ ports={
89
+ "llm": MockLLM(),
90
+ "memory": InMemoryMemory(),
91
+ }
92
+ )
93
+ else:
94
+ orchestrator = Orchestrator()
95
+
96
+ # Track node results via events
97
+ node_timings: dict[str, float] = {}
98
+
99
+ def on_node_started(event: Any) -> None:
100
+ node_timings[event.node_id] = time.perf_counter()
101
+
102
+ def on_node_completed(event: Any) -> None:
103
+ start_time = node_timings.get(event.node_id, start)
104
+ duration = (time.perf_counter() - start_time) * 1000
105
+ node_results.append(
106
+ NodeResult(
107
+ name=event.node_id,
108
+ status="completed",
109
+ output=event.output if hasattr(event, "output") else None,
110
+ duration_ms=duration,
111
+ )
112
+ )
113
+
114
+ def on_node_failed(event: Any) -> None:
115
+ start_time = node_timings.get(event.node_id, start)
116
+ duration = (time.perf_counter() - start_time) * 1000
117
+ node_results.append(
118
+ NodeResult(
119
+ name=event.node_id,
120
+ status="failed",
121
+ error=str(event.error) if hasattr(event, "error") else "Unknown error",
122
+ duration_ms=duration,
123
+ )
124
+ )
125
+
126
+ # Subscribe to events
127
+ orchestrator.on("node_started", on_node_started)
128
+ orchestrator.on("node_completed", on_node_completed)
129
+ orchestrator.on("node_failed", on_node_failed)
130
+
131
+ # Execute with timeout
132
+ try:
133
+ result = await asyncio.wait_for(
134
+ orchestrator.run(graph, request.inputs),
135
+ timeout=request.timeout,
136
+ )
137
+ success = True
138
+ final_output = result
139
+ error = None
140
+ except TimeoutError:
141
+ success = False
142
+ final_output = None
143
+ error = f"Execution timed out after {request.timeout}s"
144
+
145
+ except Exception as e:
146
+ success = False
147
+ final_output = None
148
+ error = str(e)
149
+
150
+ duration = (time.perf_counter() - start) * 1000
151
+
152
+ return ExecuteResponse(
153
+ success=success,
154
+ nodes=node_results,
155
+ final_output=final_output,
156
+ error=error,
157
+ duration_ms=duration,
158
+ )
159
+
160
+
161
+ @router.post("/dry-run")
162
+ async def dry_run(request: ExecuteRequest) -> dict[str, Any]:
163
+ """Analyze pipeline without executing.
164
+
165
+ Returns execution plan, dependency order, and estimated complexity.
166
+ """
167
+ # Add workspace root to sys.path for local module imports
168
+ try:
169
+ workspace_root = get_workspace_root()
170
+ project_root = (
171
+ workspace_root.parent if workspace_root.name == "pipelines" else workspace_root
172
+ )
173
+ project_root_str = str(project_root)
174
+ if project_root_str not in sys.path:
175
+ sys.path.insert(0, project_root_str)
176
+ except Exception:
177
+ pass
178
+
179
+ try:
180
+ from hexdag.core.pipeline_builder import YamlPipelineBuilder
181
+
182
+ builder = YamlPipelineBuilder()
183
+ graph, config = builder.build_from_yaml_string(request.content)
184
+
185
+ # Get execution order
186
+ execution_order = list(graph.topological_sort())
187
+
188
+ # Analyze dependencies
189
+ dependency_map = {}
190
+ for node_id in execution_order:
191
+ node = graph.get_node(node_id)
192
+ dependency_map[node_id] = {
193
+ "dependencies": list(node.dependencies) if node else [],
194
+ "kind": node.node_type if node and hasattr(node, "node_type") else "unknown",
195
+ }
196
+
197
+ return {
198
+ "valid": True,
199
+ "execution_order": execution_order,
200
+ "node_count": len(execution_order),
201
+ "dependency_map": dependency_map,
202
+ }
203
+
204
+ except Exception as e:
205
+ return {
206
+ "valid": False,
207
+ "error": str(e),
208
+ }