flock-core 0.3.23__py3-none-any.whl → 0.3.31__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.
- flock/__init__.py +23 -11
- flock/cli/constants.py +2 -4
- flock/cli/create_flock.py +220 -1
- flock/cli/execute_flock.py +200 -0
- flock/cli/load_flock.py +27 -7
- flock/cli/loaded_flock_cli.py +202 -0
- flock/cli/manage_agents.py +443 -0
- flock/cli/view_results.py +29 -0
- flock/cli/yaml_editor.py +283 -0
- flock/core/__init__.py +2 -2
- flock/core/api/__init__.py +11 -0
- flock/core/api/endpoints.py +222 -0
- flock/core/api/main.py +237 -0
- flock/core/api/models.py +34 -0
- flock/core/api/run_store.py +72 -0
- flock/core/api/ui/__init__.py +0 -0
- flock/core/api/ui/routes.py +271 -0
- flock/core/api/ui/utils.py +119 -0
- flock/core/flock.py +509 -388
- flock/core/flock_agent.py +384 -121
- flock/core/flock_registry.py +532 -0
- flock/core/logging/logging.py +97 -23
- flock/core/mixin/dspy_integration.py +363 -158
- flock/core/serialization/__init__.py +7 -1
- flock/core/serialization/callable_registry.py +52 -0
- flock/core/serialization/serializable.py +259 -37
- flock/core/serialization/serialization_utils.py +199 -0
- flock/evaluators/declarative/declarative_evaluator.py +2 -0
- flock/modules/memory/memory_module.py +17 -4
- flock/modules/output/output_module.py +9 -3
- flock/workflow/activities.py +2 -2
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/METADATA +6 -3
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/RECORD +36 -22
- flock/core/flock_api.py +0 -214
- flock/core/registry/agent_registry.py +0 -120
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/WHEEL +0 -0
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/entry_points.txt +0 -0
- {flock_core-0.3.23.dist-info → flock_core-0.3.31.dist-info}/licenses/LICENSE +0 -0
flock/cli/yaml_editor.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""YAML Editor for Flock CLI.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to view, edit, and validate YAML configurations
|
|
4
|
+
for Flock and FlockAgent instances.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import tempfile
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import questionary
|
|
13
|
+
import yaml
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.syntax import Syntax
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
|
|
19
|
+
from flock.core.flock import Flock
|
|
20
|
+
from flock.core.flock_agent import FlockAgent
|
|
21
|
+
from flock.core.util.cli_helper import init_console
|
|
22
|
+
|
|
23
|
+
# Create console instance
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def yaml_editor(flock_or_agent: Flock | FlockAgent | None = None):
|
|
28
|
+
"""YAML Editor main entry point.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
flock_or_agent: Optional Flock or FlockAgent instance to edit
|
|
32
|
+
"""
|
|
33
|
+
init_console()
|
|
34
|
+
console.print(Panel("[bold green]YAML Editor[/]"), justify="center")
|
|
35
|
+
|
|
36
|
+
if flock_or_agent is None:
|
|
37
|
+
# If no object provided, provide options to load from file
|
|
38
|
+
_yaml_file_browser()
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
while True:
|
|
42
|
+
init_console()
|
|
43
|
+
console.print(Panel("[bold green]YAML Editor[/]"), justify="center")
|
|
44
|
+
|
|
45
|
+
# Determine object type
|
|
46
|
+
if isinstance(flock_or_agent, Flock):
|
|
47
|
+
obj_type = "Flock"
|
|
48
|
+
console.print(
|
|
49
|
+
f"Editing [bold cyan]Flock[/] with {len(flock_or_agent._agents)} agents"
|
|
50
|
+
)
|
|
51
|
+
elif isinstance(flock_or_agent, FlockAgent):
|
|
52
|
+
obj_type = "FlockAgent"
|
|
53
|
+
console.print(
|
|
54
|
+
f"Editing [bold cyan]FlockAgent[/]: {flock_or_agent.name}"
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
console.print("[bold red]Error: Unknown object type[/]")
|
|
58
|
+
input("\nPress Enter to continue...")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
console.line()
|
|
62
|
+
|
|
63
|
+
choice = questionary.select(
|
|
64
|
+
"What would you like to do?",
|
|
65
|
+
choices=[
|
|
66
|
+
questionary.Separator(line=" "),
|
|
67
|
+
"View Current YAML",
|
|
68
|
+
"Edit YAML Directly",
|
|
69
|
+
"Abstract Editor (Visual)",
|
|
70
|
+
"Validate YAML",
|
|
71
|
+
"Save to File",
|
|
72
|
+
questionary.Separator(),
|
|
73
|
+
"Back to Main Menu",
|
|
74
|
+
],
|
|
75
|
+
).ask()
|
|
76
|
+
|
|
77
|
+
if choice == "View Current YAML":
|
|
78
|
+
_view_yaml(flock_or_agent)
|
|
79
|
+
elif choice == "Edit YAML Directly":
|
|
80
|
+
flock_or_agent = _edit_yaml_directly(flock_or_agent)
|
|
81
|
+
elif choice == "Abstract Editor (Visual)":
|
|
82
|
+
flock_or_agent = _abstract_editor(flock_or_agent)
|
|
83
|
+
elif choice == "Validate YAML":
|
|
84
|
+
_validate_yaml(flock_or_agent)
|
|
85
|
+
elif choice == "Save to File":
|
|
86
|
+
_save_to_file(flock_or_agent)
|
|
87
|
+
elif choice == "Back to Main Menu":
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
if choice != "Back to Main Menu":
|
|
91
|
+
input("\nPress Enter to continue...")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _yaml_file_browser():
|
|
95
|
+
"""Browser for YAML files to load."""
|
|
96
|
+
console.print("\n[bold]YAML File Browser[/]")
|
|
97
|
+
console.line()
|
|
98
|
+
|
|
99
|
+
current_dir = os.getcwd()
|
|
100
|
+
console.print(f"Current directory: [cyan]{current_dir}[/]")
|
|
101
|
+
|
|
102
|
+
# List .yaml/.yml files in current directory
|
|
103
|
+
yaml_files = list(Path(current_dir).glob("*.yaml")) + list(
|
|
104
|
+
Path(current_dir).glob("*.yml")
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not yaml_files:
|
|
108
|
+
console.print("[yellow]No YAML files found in current directory.[/]")
|
|
109
|
+
input("\nPress Enter to continue...")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
# Display files
|
|
113
|
+
table = Table(title="YAML Files")
|
|
114
|
+
table.add_column("Filename", style="cyan")
|
|
115
|
+
table.add_column("Size", style="green")
|
|
116
|
+
table.add_column("Last Modified", style="yellow")
|
|
117
|
+
|
|
118
|
+
for file in yaml_files:
|
|
119
|
+
table.add_row(
|
|
120
|
+
file.name, f"{file.stat().st_size} bytes", f"{file.stat().st_mtime}"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
console.print(table)
|
|
124
|
+
|
|
125
|
+
# TODO: Add file selection and loading
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _view_yaml(obj: Flock | FlockAgent):
|
|
129
|
+
"""View the YAML representation of an object.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
obj: The object to view as YAML
|
|
133
|
+
"""
|
|
134
|
+
yaml_str = obj.to_yaml()
|
|
135
|
+
|
|
136
|
+
# Display with syntax highlighting
|
|
137
|
+
syntax = Syntax(
|
|
138
|
+
yaml_str,
|
|
139
|
+
"yaml",
|
|
140
|
+
theme="monokai",
|
|
141
|
+
line_numbers=True,
|
|
142
|
+
code_width=100,
|
|
143
|
+
word_wrap=True,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
init_console()
|
|
147
|
+
console.print(Panel("[bold green]YAML View[/]"), justify="center")
|
|
148
|
+
console.print(syntax)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _edit_yaml_directly(obj: Flock | FlockAgent) -> Flock | FlockAgent:
|
|
152
|
+
"""Edit the YAML representation directly using an external editor.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
obj: The object to edit
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
The updated object
|
|
159
|
+
"""
|
|
160
|
+
# Convert to YAML
|
|
161
|
+
yaml_str = obj.to_yaml()
|
|
162
|
+
|
|
163
|
+
# Create a temporary file
|
|
164
|
+
with tempfile.NamedTemporaryFile(
|
|
165
|
+
suffix=".yaml", mode="w+", delete=False
|
|
166
|
+
) as tmp:
|
|
167
|
+
tmp.write(yaml_str)
|
|
168
|
+
tmp_path = tmp.name
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# Determine which editor to use
|
|
172
|
+
editor = os.environ.get(
|
|
173
|
+
"EDITOR", "notepad" if os.name == "nt" else "nano"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Open the editor
|
|
177
|
+
console.print(
|
|
178
|
+
f"\nOpening {editor} to edit YAML. Save and exit when done."
|
|
179
|
+
)
|
|
180
|
+
subprocess.call([editor, tmp_path])
|
|
181
|
+
|
|
182
|
+
# Read updated YAML
|
|
183
|
+
with open(tmp_path) as f:
|
|
184
|
+
updated_yaml = f.read()
|
|
185
|
+
|
|
186
|
+
# Parse back to object
|
|
187
|
+
try:
|
|
188
|
+
if isinstance(obj, Flock):
|
|
189
|
+
updated_obj = Flock.from_yaml(updated_yaml)
|
|
190
|
+
console.print("\n[green]✓[/] YAML parsed successfully!")
|
|
191
|
+
return updated_obj
|
|
192
|
+
elif isinstance(obj, FlockAgent):
|
|
193
|
+
updated_obj = FlockAgent.from_yaml(updated_yaml)
|
|
194
|
+
console.print("\n[green]✓[/] YAML parsed successfully!")
|
|
195
|
+
return updated_obj
|
|
196
|
+
except Exception as e:
|
|
197
|
+
console.print(f"\n[bold red]Error parsing YAML:[/] {e!s}")
|
|
198
|
+
console.print("\nKeeping original object.")
|
|
199
|
+
return obj
|
|
200
|
+
|
|
201
|
+
finally:
|
|
202
|
+
# Clean up the temporary file
|
|
203
|
+
try:
|
|
204
|
+
os.unlink(tmp_path)
|
|
205
|
+
except:
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _abstract_editor(obj: Flock | FlockAgent) -> Flock | FlockAgent:
|
|
210
|
+
"""Edit object using an abstract form-based editor.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
obj: The object to edit
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The updated object
|
|
217
|
+
"""
|
|
218
|
+
console.print("\n[yellow]Abstract visual editor not yet implemented.[/]")
|
|
219
|
+
console.print("Will provide a form-based editor for each field.")
|
|
220
|
+
|
|
221
|
+
# For now, just return the original object
|
|
222
|
+
return obj
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _validate_yaml(obj: Flock | FlockAgent):
|
|
226
|
+
"""Validate the YAML representation of an object.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
obj: The object to validate
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
yaml_str = obj.to_yaml()
|
|
233
|
+
|
|
234
|
+
# Attempt to parse with PyYAML
|
|
235
|
+
yaml.safe_load(yaml_str)
|
|
236
|
+
|
|
237
|
+
# Attempt to deserialize back to object
|
|
238
|
+
if isinstance(obj, Flock):
|
|
239
|
+
Flock.from_yaml(yaml_str)
|
|
240
|
+
elif isinstance(obj, FlockAgent):
|
|
241
|
+
FlockAgent.from_yaml(yaml_str)
|
|
242
|
+
|
|
243
|
+
console.print("\n[green]✓[/] YAML validation successful!")
|
|
244
|
+
except Exception as e:
|
|
245
|
+
console.print(f"\n[bold red]YAML validation failed:[/] {e!s}")
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _save_to_file(obj: Flock | FlockAgent):
|
|
249
|
+
"""Save object to a YAML file.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
obj: The object to save
|
|
253
|
+
"""
|
|
254
|
+
# Determine default filename based on object type
|
|
255
|
+
if isinstance(obj, Flock):
|
|
256
|
+
default_name = "my_flock.flock.yaml"
|
|
257
|
+
elif isinstance(obj, FlockAgent):
|
|
258
|
+
default_name = f"{obj.name}.agent.yaml"
|
|
259
|
+
else:
|
|
260
|
+
default_name = "unknown.yaml"
|
|
261
|
+
|
|
262
|
+
# Get file path
|
|
263
|
+
file_path = questionary.text(
|
|
264
|
+
"Enter file path to save YAML:",
|
|
265
|
+
default=default_name,
|
|
266
|
+
).ask()
|
|
267
|
+
|
|
268
|
+
# Ensure the file has the correct extension
|
|
269
|
+
if not file_path.endswith((".yaml", ".yml")):
|
|
270
|
+
file_path += ".yaml"
|
|
271
|
+
|
|
272
|
+
# Create directory if it doesn't exist
|
|
273
|
+
save_path = Path(file_path)
|
|
274
|
+
save_path.parent.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
# Save to file
|
|
278
|
+
with open(file_path, "w") as f:
|
|
279
|
+
f.write(obj.to_yaml())
|
|
280
|
+
|
|
281
|
+
console.print(f"\n[green]✓[/] Saved to {file_path}")
|
|
282
|
+
except Exception as e:
|
|
283
|
+
console.print(f"\n[bold red]Error saving file:[/] {e!s}")
|
flock/core/__init__.py
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
from flock.core.flock import Flock
|
|
4
4
|
from flock.core.flock_agent import FlockAgent
|
|
5
|
-
from flock.core.flock_api import FlockAPI
|
|
6
5
|
from flock.core.flock_evaluator import FlockEvaluator, FlockEvaluatorConfig
|
|
7
6
|
from flock.core.flock_factory import FlockFactory
|
|
8
7
|
from flock.core.flock_module import FlockModule, FlockModuleConfig
|
|
8
|
+
from flock.core.flock_registry import FlockRegistry
|
|
9
9
|
|
|
10
10
|
__all__ = [
|
|
11
11
|
"Flock",
|
|
12
|
-
"FlockAPI",
|
|
13
12
|
"FlockAgent",
|
|
14
13
|
"FlockEvaluator",
|
|
15
14
|
"FlockEvaluatorConfig",
|
|
16
15
|
"FlockFactory",
|
|
17
16
|
"FlockModule",
|
|
18
17
|
"FlockModuleConfig",
|
|
18
|
+
"FlockRegistry",
|
|
19
19
|
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# src/flock/core/api/endpoints.py
|
|
2
|
+
"""FastAPI endpoints for the Flock API."""
|
|
3
|
+
|
|
4
|
+
import html # For escaping
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import TYPE_CHECKING # Added Any for type hinting clarity
|
|
7
|
+
|
|
8
|
+
from fastapi import (
|
|
9
|
+
APIRouter,
|
|
10
|
+
BackgroundTasks,
|
|
11
|
+
HTTPException,
|
|
12
|
+
Request as FastAPIRequest,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Import HTMLResponse for the UI form endpoint
|
|
16
|
+
from fastapi.responses import HTMLResponse
|
|
17
|
+
|
|
18
|
+
from flock.core.logging.logging import get_logger
|
|
19
|
+
|
|
20
|
+
# Import models and UI utils
|
|
21
|
+
from .models import FlockAPIRequest, FlockAPIResponse
|
|
22
|
+
|
|
23
|
+
# Import UI utils - assuming they are now in ui/utils.py
|
|
24
|
+
|
|
25
|
+
# Use TYPE_CHECKING to avoid circular imports for type hints
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from flock.core.flock import Flock
|
|
28
|
+
|
|
29
|
+
from .main import FlockAPI
|
|
30
|
+
from .run_store import RunStore
|
|
31
|
+
|
|
32
|
+
logger = get_logger("api.endpoints")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Factory function to create the router with dependencies
|
|
36
|
+
def create_api_router(flock_api: "FlockAPI") -> APIRouter:
|
|
37
|
+
"""Creates the APIRouter and defines endpoints, injecting dependencies."""
|
|
38
|
+
router = APIRouter()
|
|
39
|
+
# Get dependencies from the main FlockAPI instance passed in
|
|
40
|
+
run_store: RunStore = flock_api.run_store
|
|
41
|
+
flock_instance: Flock = flock_api.flock
|
|
42
|
+
|
|
43
|
+
# --- API Endpoints ---
|
|
44
|
+
@router.post("/run/flock", response_model=FlockAPIResponse, tags=["API"])
|
|
45
|
+
async def run_flock_json(
|
|
46
|
+
request: FlockAPIRequest, background_tasks: BackgroundTasks
|
|
47
|
+
):
|
|
48
|
+
"""Run a flock workflow starting with the specified agent (expects JSON)."""
|
|
49
|
+
run_id = None
|
|
50
|
+
try:
|
|
51
|
+
run_id = str(uuid.uuid4())
|
|
52
|
+
run_store.create_run(run_id) # Use RunStore
|
|
53
|
+
response = run_store.get_run(
|
|
54
|
+
run_id
|
|
55
|
+
) # Get initial response from store
|
|
56
|
+
|
|
57
|
+
processed_inputs = request.inputs if request.inputs else {}
|
|
58
|
+
logger.info(
|
|
59
|
+
f"API request: run flock '{request.agent_name}' (run_id: {run_id})",
|
|
60
|
+
inputs=processed_inputs,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
if request.async_run:
|
|
64
|
+
logger.debug(
|
|
65
|
+
f"Running flock '{request.agent_name}' asynchronously (run_id: {run_id})"
|
|
66
|
+
)
|
|
67
|
+
# Call the helper method on the passed FlockAPI instance
|
|
68
|
+
background_tasks.add_task(
|
|
69
|
+
flock_api._run_flock,
|
|
70
|
+
run_id,
|
|
71
|
+
request.agent_name,
|
|
72
|
+
processed_inputs,
|
|
73
|
+
)
|
|
74
|
+
run_store.update_run_status(run_id, "running")
|
|
75
|
+
response.status = "running" # Update local response copy too
|
|
76
|
+
else:
|
|
77
|
+
logger.debug(
|
|
78
|
+
f"Running flock '{request.agent_name}' synchronously (run_id: {run_id})"
|
|
79
|
+
)
|
|
80
|
+
# Call the helper method on the passed FlockAPI instance
|
|
81
|
+
await flock_api._run_flock(
|
|
82
|
+
run_id, request.agent_name, processed_inputs
|
|
83
|
+
)
|
|
84
|
+
response = run_store.get_run(
|
|
85
|
+
run_id
|
|
86
|
+
) # Fetch updated status/result
|
|
87
|
+
|
|
88
|
+
return response
|
|
89
|
+
except ValueError as ve:
|
|
90
|
+
logger.error(f"Value error starting run: {ve}")
|
|
91
|
+
if run_id:
|
|
92
|
+
run_store.update_run_status(run_id, "failed", str(ve))
|
|
93
|
+
raise HTTPException(status_code=400, detail=str(ve))
|
|
94
|
+
except Exception as e:
|
|
95
|
+
error_msg = f"Internal server error: {type(e).__name__}"
|
|
96
|
+
logger.error(f"Error starting run: {e!s}", exc_info=True)
|
|
97
|
+
if run_id:
|
|
98
|
+
run_store.update_run_status(run_id, "failed", error_msg)
|
|
99
|
+
raise HTTPException(status_code=500, detail=error_msg)
|
|
100
|
+
|
|
101
|
+
@router.get("/run/{run_id}", response_model=FlockAPIResponse, tags=["API"])
|
|
102
|
+
async def get_run_status(run_id: str):
|
|
103
|
+
"""Get the status of a specific run."""
|
|
104
|
+
logger.debug(f"API request: get status for run_id: {run_id}")
|
|
105
|
+
run_data = run_store.get_run(run_id)
|
|
106
|
+
if not run_data:
|
|
107
|
+
logger.warning(f"Run ID not found: {run_id}")
|
|
108
|
+
raise HTTPException(status_code=404, detail="Run not found")
|
|
109
|
+
return run_data
|
|
110
|
+
|
|
111
|
+
@router.get("/agents", tags=["API"])
|
|
112
|
+
async def list_agents():
|
|
113
|
+
"""List all available agents."""
|
|
114
|
+
logger.debug("API request: list agents")
|
|
115
|
+
# Access flock instance via factory closure
|
|
116
|
+
agents_list = [
|
|
117
|
+
{"name": agent.name, "description": agent.description or agent.name}
|
|
118
|
+
for agent in flock_instance.agents.values()
|
|
119
|
+
]
|
|
120
|
+
return {"agents": agents_list}
|
|
121
|
+
|
|
122
|
+
# --- UI Form Endpoint ---
|
|
123
|
+
@router.post("/ui/run-agent-form", response_class=HTMLResponse, tags=["UI"])
|
|
124
|
+
async def run_flock_form(fastapi_req: FastAPIRequest):
|
|
125
|
+
"""Endpoint to handle form submissions from the UI."""
|
|
126
|
+
run_id = None
|
|
127
|
+
try:
|
|
128
|
+
form_data = await fastapi_req.form()
|
|
129
|
+
agent_name = form_data.get("agent_name")
|
|
130
|
+
if not agent_name:
|
|
131
|
+
logger.warning("UI form submission missing agent_name")
|
|
132
|
+
return HTMLResponse(
|
|
133
|
+
'<div id="result-content" class="error-message">Error: Agent name not provided.</div>',
|
|
134
|
+
status_code=400,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
logger.info(f"UI Form submission for agent: {agent_name}")
|
|
138
|
+
form_inputs = {}
|
|
139
|
+
# Access flock instance via factory closure
|
|
140
|
+
agent_def = flock_instance.agents.get(agent_name)
|
|
141
|
+
# Use helper from flock_api instance for parsing
|
|
142
|
+
defined_input_fields = (
|
|
143
|
+
flock_api._parse_input_spec(agent_def.input or "")
|
|
144
|
+
if agent_def
|
|
145
|
+
else []
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
for key, value in form_data.items():
|
|
149
|
+
if key.startswith("inputs."):
|
|
150
|
+
form_inputs[key[len("inputs.") :]] = value
|
|
151
|
+
for field in defined_input_fields: # Handle checkboxes
|
|
152
|
+
if (
|
|
153
|
+
field["html_type"] == "checkbox"
|
|
154
|
+
and field["name"] not in form_inputs
|
|
155
|
+
):
|
|
156
|
+
form_inputs[field["name"]] = False
|
|
157
|
+
elif (
|
|
158
|
+
field["html_type"] == "checkbox"
|
|
159
|
+
and field["name"] in form_inputs
|
|
160
|
+
):
|
|
161
|
+
form_inputs[field["name"]] = True
|
|
162
|
+
|
|
163
|
+
logger.debug(f"Parsed form inputs for UI run: {form_inputs}")
|
|
164
|
+
run_id = str(uuid.uuid4())
|
|
165
|
+
run_store.create_run(run_id) # Use RunStore
|
|
166
|
+
logger.debug(
|
|
167
|
+
f"Running flock '{agent_name}' synchronously from UI (run_id: {run_id})"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Call helper method on flock_api instance
|
|
171
|
+
await flock_api._run_flock(run_id, agent_name, form_inputs)
|
|
172
|
+
|
|
173
|
+
final_status = run_store.get_run(run_id)
|
|
174
|
+
if final_status and final_status.status == "completed":
|
|
175
|
+
# Use helper from flock_api instance for formatting
|
|
176
|
+
formatted_html = flock_api._format_result_to_html(
|
|
177
|
+
final_status.result
|
|
178
|
+
)
|
|
179
|
+
logger.info(f"UI run completed successfully (run_id: {run_id})")
|
|
180
|
+
return HTMLResponse(
|
|
181
|
+
f"<div id='result-content'>{formatted_html}</div>"
|
|
182
|
+
) # Wrap in target div
|
|
183
|
+
elif final_status and final_status.status == "failed":
|
|
184
|
+
logger.error(
|
|
185
|
+
f"UI run failed (run_id: {run_id}): {final_status.error}"
|
|
186
|
+
)
|
|
187
|
+
error_msg = html.escape(final_status.error or "Unknown error")
|
|
188
|
+
return HTMLResponse(
|
|
189
|
+
f"<div id='result-content' class='error-message'>Run Failed: {error_msg}</div>",
|
|
190
|
+
status_code=500,
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
status_str = (
|
|
194
|
+
final_status.status if final_status else "Not Found"
|
|
195
|
+
)
|
|
196
|
+
logger.warning(
|
|
197
|
+
f"UI run {run_id} ended in unexpected state: {status_str}"
|
|
198
|
+
)
|
|
199
|
+
return HTMLResponse(
|
|
200
|
+
f"<div id='result-content' class='error-message'>Run ended unexpectedly. Status: {status_str}</div>",
|
|
201
|
+
status_code=500,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
except ValueError as ve:
|
|
205
|
+
logger.error(f"Value error processing UI form run: {ve}")
|
|
206
|
+
if run_id:
|
|
207
|
+
run_store.update_run_status(run_id, "failed", str(ve))
|
|
208
|
+
return HTMLResponse(
|
|
209
|
+
f"<div id='result-content' class='error-message'>Error: {html.escape(str(ve))}</div>",
|
|
210
|
+
status_code=400,
|
|
211
|
+
)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
error_msg = f"Internal server error: {type(e).__name__}"
|
|
214
|
+
logger.error(f"Error processing UI form run: {e!s}", exc_info=True)
|
|
215
|
+
if run_id:
|
|
216
|
+
run_store.update_run_status(run_id, "failed", error_msg)
|
|
217
|
+
return HTMLResponse(
|
|
218
|
+
f"<div id='result-content' class='error-message'>{html.escape(error_msg)}</div>",
|
|
219
|
+
status_code=500,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return router
|