Add web data and server workflow plugins with tests

Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-10 15:17:49 +00:00
parent 4f9d04c9f1
commit f129c8eeb8
27 changed files with 579 additions and 1 deletions

View File

@@ -89,5 +89,29 @@
"var.delete": "autometabuilder.workflow.plugins.var.var_delete.run",
"var.exists": "autometabuilder.workflow.plugins.var.var_exists.run",
"var.get": "autometabuilder.workflow.plugins.var.var_get.run",
"var.set": "autometabuilder.workflow.plugins.var.var_set.run"
"var.set": "autometabuilder.workflow.plugins.var.var_set.run",
"web.build_context": "autometabuilder.workflow.plugins.web.web_build_context.run",
"web.build_prompt_yaml": "autometabuilder.workflow.plugins.web.web_build_prompt_yaml.run",
"web.create_flask_app": "autometabuilder.workflow.plugins.web.web_create_flask_app.run",
"web.create_translation": "autometabuilder.workflow.plugins.web.web_create_translation.run",
"web.delete_translation": "autometabuilder.workflow.plugins.web.web_delete_translation.run",
"web.get_env_vars": "autometabuilder.workflow.plugins.web.web_get_env_vars.run",
"web.get_navigation_items": "autometabuilder.workflow.plugins.web.web_get_navigation_items.run",
"web.get_prompt_content": "autometabuilder.workflow.plugins.web.web_get_prompt_content.run",
"web.get_recent_logs": "autometabuilder.workflow.plugins.web.web_get_recent_logs.run",
"web.get_ui_messages": "autometabuilder.workflow.plugins.web.web_get_ui_messages.run",
"web.get_workflow_content": "autometabuilder.workflow.plugins.web.web_get_workflow_content.run",
"web.list_translations": "autometabuilder.workflow.plugins.web.web_list_translations.run",
"web.load_messages": "autometabuilder.workflow.plugins.web.web_load_messages.run",
"web.load_translation": "autometabuilder.workflow.plugins.web.web_load_translation.run",
"web.load_workflow_packages": "autometabuilder.workflow.plugins.web.web_load_workflow_packages.run",
"web.persist_env_vars": "autometabuilder.workflow.plugins.web.web_persist_env_vars.run",
"web.read_json": "autometabuilder.workflow.plugins.web.web_read_json.run",
"web.register_blueprint": "autometabuilder.workflow.plugins.web.web_register_blueprint.run",
"web.start_server": "autometabuilder.workflow.plugins.web.web_start_server.run",
"web.summarize_workflow_packages": "autometabuilder.workflow.plugins.web.web_summarize_workflow_packages.run",
"web.update_translation": "autometabuilder.workflow.plugins.web.web_update_translation.run",
"web.write_messages_dir": "autometabuilder.workflow.plugins.web.web_write_messages_dir.run",
"web.write_prompt": "autometabuilder.workflow.plugins.web.web_write_prompt.run",
"web.write_workflow": "autometabuilder.workflow.plugins.web.web_write_workflow.run"
}

View File

@@ -0,0 +1 @@
"""Web data access workflow plugins."""

View File

@@ -0,0 +1,17 @@
"""Workflow plugin: build context for API."""
import os
from ....web.routes.context import build_context
def run(_runtime, _inputs):
"""
Build the complete context object for the web UI.
This includes logs, env vars, translations, metadata, navigation,
prompt, workflow, packages, messages, and status.
Returns:
dict: Complete context object
"""
context = build_context()
return {"result": context}

View File

@@ -0,0 +1,12 @@
"""Workflow plugin: build prompt YAML."""
from ....web.data.prompt import build_prompt_yaml
def run(_runtime, inputs):
"""Build prompt YAML from system and user content."""
system_content = inputs.get("system_content")
user_content = inputs.get("user_content")
model = inputs.get("model")
yaml_content = build_prompt_yaml(system_content, user_content, model)
return {"result": yaml_content}

View File

@@ -0,0 +1,28 @@
"""Workflow plugin: create Flask app."""
from flask import Flask
def run(runtime, inputs):
"""
Create a Flask application instance.
Inputs:
name: Application name (default: __name__)
config: Dictionary of Flask configuration options
Returns:
dict: Contains the Flask app in result
"""
name = inputs.get("name", "__main__")
config = inputs.get("config", {})
app = Flask(name)
# Apply configuration
for key, value in config.items():
app.config[key] = value
# Store app in runtime context for other plugins to use
runtime.context["flask_app"] = app
return {"result": app, "message": "Flask app created"}

View File

@@ -0,0 +1,12 @@
"""Workflow plugin: create translation."""
from ....web.data.translations import create_translation
def run(_runtime, inputs):
"""Create a new translation."""
lang = inputs.get("lang")
if not lang:
return {"error": "lang is required"}
created = create_translation(lang)
return {"result": created}

View File

@@ -0,0 +1,12 @@
"""Workflow plugin: delete translation."""
from ....web.data.translations import delete_translation
def run(_runtime, inputs):
"""Delete a translation."""
lang = inputs.get("lang")
if not lang:
return {"error": "lang is required"}
deleted = delete_translation(lang)
return {"result": deleted}

View File

@@ -0,0 +1,8 @@
"""Workflow plugin: get environment variables."""
from ....web.data.env import get_env_vars
def run(_runtime, _inputs):
"""Get environment variables from .env file."""
env_vars = get_env_vars()
return {"result": env_vars}

View File

@@ -0,0 +1,8 @@
"""Workflow plugin: get navigation items."""
from ....web.data.navigation import get_navigation_items
def run(_runtime, _inputs):
"""Get navigation items."""
items = get_navigation_items()
return {"result": items}

View File

@@ -0,0 +1,8 @@
"""Workflow plugin: get prompt content."""
from ....web.data.prompt import get_prompt_content
def run(_runtime, _inputs):
"""Get prompt content from prompt file."""
content = get_prompt_content()
return {"result": content}

View File

@@ -0,0 +1,9 @@
"""Workflow plugin: get recent logs."""
from ....web.data.logs import get_recent_logs
def run(_runtime, inputs):
"""Get recent log entries."""
lines = inputs.get("lines", 50)
logs = get_recent_logs(lines)
return {"result": logs}

View File

@@ -0,0 +1,17 @@
"""Workflow plugin: get UI messages."""
from ....web.data.translations import get_ui_messages
def run(_runtime, inputs):
"""
Get UI messages for a specific language with fallback to English.
Inputs:
lang: Language code (default: en)
Returns:
dict: UI messages with __lang key indicating the language
"""
lang = inputs.get("lang", "en")
messages = get_ui_messages(lang)
return {"result": messages}

View File

@@ -0,0 +1,8 @@
"""Workflow plugin: get workflow content."""
from ....web.data.workflow import get_workflow_content
def run(_runtime, _inputs):
"""Get workflow content from workflow file."""
content = get_workflow_content()
return {"result": content}

View File

@@ -0,0 +1,8 @@
"""Workflow plugin: list translations."""
from ....web.data.translations import list_translations
def run(_runtime, _inputs):
"""List all available translations."""
translations = list_translations()
return {"result": translations}

View File

@@ -0,0 +1,13 @@
"""Workflow plugin: load messages."""
from pathlib import Path
from ....web.data.messages_io import load_messages
def run(_runtime, inputs):
"""Load translation messages from path."""
path = inputs.get("path")
if not path:
return {"error": "path is required"}
messages = load_messages(Path(path))
return {"result": messages}

View File

@@ -0,0 +1,9 @@
"""Workflow plugin: load translation."""
from ....web.data.translations import load_translation
def run(_runtime, inputs):
"""Load translation for a specific language."""
lang = inputs.get("lang", "en")
translation = load_translation(lang)
return {"result": translation}

View File

@@ -0,0 +1,8 @@
"""Workflow plugin: load workflow packages."""
from ....web.data.workflow import load_workflow_packages
def run(_runtime, _inputs):
"""Load all workflow packages."""
packages = load_workflow_packages()
return {"result": packages}

View File

@@ -0,0 +1,9 @@
"""Workflow plugin: persist environment variables."""
from ....web.data.env import persist_env_vars
def run(_runtime, inputs):
"""Persist environment variables to .env file."""
updates = inputs.get("updates", {})
persist_env_vars(updates)
return {"result": "Environment variables persisted"}

View File

@@ -0,0 +1,13 @@
"""Workflow plugin: read JSON file."""
from pathlib import Path
from ....web.data.json_utils import read_json
def run(_runtime, inputs):
"""Read JSON file."""
path = inputs.get("path")
if not path:
return {"error": "path is required"}
json_data = read_json(Path(path))
return {"result": json_data}

View File

@@ -0,0 +1,29 @@
"""Workflow plugin: register Flask blueprint."""
def run(runtime, inputs):
"""
Register a Flask blueprint with the Flask app.
Inputs:
blueprint_path: Dotted path to the blueprint (e.g., "autometabuilder.web.routes.context.context_bp")
Returns:
dict: Success indicator
"""
from ....loaders.callable_loader import load_callable
app = runtime.context.get("flask_app")
if not app:
return {"error": "Flask app not found in context. Run web.create_flask_app first."}
blueprint_path = inputs.get("blueprint_path")
if not blueprint_path:
return {"error": "blueprint_path is required"}
try:
blueprint = load_callable(blueprint_path)
app.register_blueprint(blueprint)
return {"result": f"Blueprint {blueprint_path} registered"}
except Exception as e:
return {"error": f"Failed to register blueprint: {str(e)}"}

View File

@@ -0,0 +1,27 @@
"""Workflow plugin: start Flask server."""
def run(runtime, inputs):
"""
Start the Flask web server.
Inputs:
host: Host address (default: 0.0.0.0)
port: Port number (default: 8000)
debug: Enable debug mode (default: False)
Returns:
dict: Success indicator (note: this blocks until server stops)
"""
app = runtime.context.get("flask_app")
if not app:
return {"error": "Flask app not found in context. Run web.create_flask_app first."}
host = inputs.get("host", "0.0.0.0")
port = inputs.get("port", 8000)
debug = inputs.get("debug", False)
# This will block until the server is stopped
app.run(host=host, port=port, debug=debug)
return {"result": "Server stopped"}

View File

@@ -0,0 +1,9 @@
"""Workflow plugin: summarize workflow packages."""
from ....web.data.workflow import summarize_workflow_packages
def run(_runtime, inputs):
"""Summarize workflow packages."""
packages = inputs.get("packages", [])
summary = summarize_workflow_packages(packages)
return {"result": summary}

View File

@@ -0,0 +1,14 @@
"""Workflow plugin: update translation."""
from ....web.data.translations import update_translation
def run(_runtime, inputs):
"""Update an existing translation."""
lang = inputs.get("lang")
payload = inputs.get("payload", {})
if not lang:
return {"error": "lang is required"}
updated = update_translation(lang, payload)
return {"result": updated}

View File

@@ -0,0 +1,15 @@
"""Workflow plugin: write messages directory."""
from pathlib import Path
from ....web.data.messages_io import write_messages_dir
def run(_runtime, inputs):
"""Write messages to directory."""
base_dir = inputs.get("base_dir")
payload_content = inputs.get("payload_content", {})
if not base_dir:
return {"error": "base_dir is required"}
write_messages_dir(Path(base_dir), payload_content)
return {"result": "Messages written successfully"}

View File

@@ -0,0 +1,9 @@
"""Workflow plugin: write prompt."""
from ....web.data.prompt import write_prompt
def run(_runtime, inputs):
"""Write prompt content to file."""
content = inputs.get("content", "")
write_prompt(content)
return {"result": "Prompt written successfully"}

View File

@@ -0,0 +1,9 @@
"""Workflow plugin: write workflow."""
from ....web.data.workflow import write_workflow
def run(_runtime, inputs):
"""Write workflow content to file."""
content = inputs.get("content", "")
write_workflow(content)
return {"result": "Workflow written successfully"}

View File

@@ -0,0 +1,242 @@
"""Tests for web workflow plugins."""
import os
import tempfile
from pathlib import Path
from autometabuilder.workflow.plugin_registry import PluginRegistry, load_plugin_map
from autometabuilder.workflow.runtime import WorkflowRuntime
class MockLogger:
"""Mock logger for testing."""
def info(self, *args, **kwargs):
pass
def debug(self, *args, **kwargs):
pass
def error(self, *args, **kwargs):
pass
def create_test_runtime():
"""Create a test runtime with empty context."""
logger = MockLogger()
return WorkflowRuntime(context={}, store={}, tool_runner=None, logger=logger)
def test_plugin_map_includes_web_plugins():
"""Test that plugin map includes all new web plugins."""
plugin_map = load_plugin_map()
# Test web data plugins
assert "web.get_env_vars" in plugin_map
assert "web.persist_env_vars" in plugin_map
assert "web.get_recent_logs" in plugin_map
assert "web.read_json" in plugin_map
assert "web.load_messages" in plugin_map
assert "web.write_messages_dir" in plugin_map
assert "web.get_navigation_items" in plugin_map
assert "web.get_prompt_content" in plugin_map
assert "web.write_prompt" in plugin_map
assert "web.build_prompt_yaml" in plugin_map
assert "web.get_workflow_content" in plugin_map
assert "web.write_workflow" in plugin_map
assert "web.load_workflow_packages" in plugin_map
assert "web.summarize_workflow_packages" in plugin_map
# Test translation plugins
assert "web.load_translation" in plugin_map
assert "web.list_translations" in plugin_map
assert "web.create_translation" in plugin_map
assert "web.update_translation" in plugin_map
assert "web.delete_translation" in plugin_map
assert "web.get_ui_messages" in plugin_map
# Test Flask/server plugins
assert "web.create_flask_app" in plugin_map
assert "web.register_blueprint" in plugin_map
assert "web.start_server" in plugin_map
assert "web.build_context" in plugin_map
def test_web_read_json_plugin():
"""Test web.read_json plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.read_json")
assert plugin is not None
# Test with non-existent file (should return empty dict)
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
f.write('{"test": "value"}')
temp_path = f.name
try:
result = plugin(runtime, {"path": temp_path})
assert "result" in result
assert result["result"]["test"] == "value"
finally:
os.unlink(temp_path)
def test_web_build_prompt_yaml_plugin():
"""Test web.build_prompt_yaml plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.build_prompt_yaml")
assert plugin is not None
result = plugin(runtime, {
"system_content": "You are a helpful assistant",
"user_content": "Help me with coding",
"model": "openai/gpt-4o"
})
assert "result" in result
yaml_content = result["result"]
assert "messages:" in yaml_content
assert "role: system" in yaml_content
assert "role: user" in yaml_content
assert "model: openai/gpt-4o" in yaml_content
def test_web_create_flask_app_plugin():
"""Test web.create_flask_app plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.create_flask_app")
assert plugin is not None
result = plugin(runtime, {
"name": "test_app",
"config": {"JSON_SORT_KEYS": False}
})
assert "result" in result
assert runtime.context.get("flask_app") is not None
app = runtime.context["flask_app"]
assert app.config["JSON_SORT_KEYS"] is False
def test_web_register_blueprint_plugin():
"""Test web.register_blueprint plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
# First create a Flask app
create_app_plugin = registry.get("web.create_flask_app")
create_app_plugin(runtime, {"name": "test_app"})
# Now test registering a blueprint
plugin = registry.get("web.register_blueprint")
assert plugin is not None
result = plugin(runtime, {
"blueprint_path": "autometabuilder.web.routes.context.context_bp"
})
assert "result" in result
assert "registered" in result["result"]
def test_web_get_ui_messages_plugin():
"""Test web.get_ui_messages plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.get_ui_messages")
assert plugin is not None
result = plugin(runtime, {"lang": "en"})
assert "result" in result
assert isinstance(result["result"], dict)
assert result["result"].get("__lang") == "en"
def test_web_list_translations_plugin():
"""Test web.list_translations plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.list_translations")
assert plugin is not None
result = plugin(runtime, {})
assert "result" in result
assert isinstance(result["result"], dict)
def test_web_load_workflow_packages_plugin():
"""Test web.load_workflow_packages plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.load_workflow_packages")
assert plugin is not None
result = plugin(runtime, {})
assert "result" in result
assert isinstance(result["result"], list)
def test_web_summarize_workflow_packages_plugin():
"""Test web.summarize_workflow_packages plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.summarize_workflow_packages")
assert plugin is not None
packages = [
{
"id": "test_pkg",
"name": "Test Package",
"description": "A test package",
"version": "1.0.0"
}
]
result = plugin(runtime, {"packages": packages})
assert "result" in result
assert isinstance(result["result"], list)
assert len(result["result"]) == 1
assert result["result"][0]["id"] == "test_pkg"
def test_web_build_context_plugin():
"""Test web.build_context plugin."""
plugin_map = load_plugin_map()
registry = PluginRegistry(plugin_map)
runtime = create_test_runtime()
plugin = registry.get("web.build_context")
assert plugin is not None
result = plugin(runtime, {})
assert "result" in result
context = result["result"]
# Verify expected keys in context
assert "logs" in context
assert "env_vars" in context
assert "translations" in context
assert "metadata" in context
assert "navigation" in context
assert "status" in context