mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-26 06:44:58 +00:00
refactor(workflow): convert all plugins to class/struct + factory pattern
- Python: class extending NodeExecutor + factory.py (80+ plugins) - TypeScript: class implements NodeExecutor + factory.ts (7 groups, 116 classes) - Go: struct with methods + factory.go (36 plugins) - Rust: struct impl NodeExecutor trait + factory.rs (54 plugins) - Mojo: struct + factory.mojo (11 plugins) All package.json files now include: - files array listing source files - metadata.class/struct field - metadata.entrypoint field This enables a unified plugin loading system across all languages with no import side effects (Spring-style DI pattern). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
"""Workflow plugin: AI request."""
|
||||
|
||||
from tenacity import retry, stop_after_attempt, wait_exponential
|
||||
|
||||
from ...base import NodeExecutor
|
||||
|
||||
|
||||
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
|
||||
def _get_completion(client, model, messages, tools):
|
||||
@@ -15,25 +18,32 @@ def _get_completion(client, model, messages, tools):
|
||||
)
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Invoke the model with current messages."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
response = _get_completion(
|
||||
runtime.context["client"],
|
||||
runtime.context["model_name"],
|
||||
messages,
|
||||
runtime.context["tools"]
|
||||
)
|
||||
resp_msg = response.choices[0].message
|
||||
runtime.logger.info(
|
||||
resp_msg.content
|
||||
if resp_msg.content
|
||||
else runtime.context["msgs"]["info_tool_call_requested"]
|
||||
)
|
||||
messages.append(resp_msg)
|
||||
tool_calls = getattr(resp_msg, "tool_calls", None) or []
|
||||
return {
|
||||
"response": resp_msg,
|
||||
"has_tool_calls": bool(tool_calls),
|
||||
"tool_calls_count": len(tool_calls)
|
||||
}
|
||||
class CoreAiRequest(NodeExecutor):
|
||||
"""Invoke the AI model with current messages."""
|
||||
|
||||
node_type = "core.ai_request"
|
||||
category = "core"
|
||||
description = "Invoke the AI model with current messages and return the response"
|
||||
|
||||
def execute(self, inputs, runtime=None):
|
||||
"""Invoke the model with current messages."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
response = _get_completion(
|
||||
runtime.context["client"],
|
||||
runtime.context["model_name"],
|
||||
messages,
|
||||
runtime.context["tools"]
|
||||
)
|
||||
resp_msg = response.choices[0].message
|
||||
runtime.logger.info(
|
||||
resp_msg.content
|
||||
if resp_msg.content
|
||||
else runtime.context["msgs"]["info_tool_call_requested"]
|
||||
)
|
||||
messages.append(resp_msg)
|
||||
tool_calls = getattr(resp_msg, "tool_calls", None) or []
|
||||
return {
|
||||
"response": resp_msg,
|
||||
"has_tool_calls": bool(tool_calls),
|
||||
"tool_calls_count": len(tool_calls)
|
||||
}
|
||||
|
||||
8
workflow/plugins/python/core/core_ai_request/factory.py
Normal file
8
workflow/plugins/python/core/core_ai_request/factory.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Factory for CoreAiRequest plugin."""
|
||||
|
||||
from .core_ai_request import CoreAiRequest
|
||||
|
||||
|
||||
def create():
|
||||
"""Create a new CoreAiRequest instance."""
|
||||
return CoreAiRequest()
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@metabuilder/core_ai_request",
|
||||
"version": "1.0.0",
|
||||
"description": "core_ai_request plugin",
|
||||
"description": "Invoke the AI model with current messages and return the response",
|
||||
"author": "MetaBuilder",
|
||||
"license": "MIT",
|
||||
"keywords": ["core", "workflow", "plugin"],
|
||||
"main": "core_ai_request.py",
|
||||
"files": ["core_ai_request.py", "factory.py"],
|
||||
"metadata": {
|
||||
"plugin_type": "core.ai_request",
|
||||
"category": "core"
|
||||
"category": "core",
|
||||
"class": "CoreAiRequest",
|
||||
"entrypoint": "execute"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
"""Workflow plugin: append context message."""
|
||||
|
||||
from ...base import NodeExecutor
|
||||
|
||||
def run(runtime, inputs):
|
||||
|
||||
class CoreAppendContextMessage(NodeExecutor):
|
||||
"""Append context to the message list."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
context_val = inputs.get("context")
|
||||
if context_val:
|
||||
messages.append({
|
||||
"role": "system",
|
||||
"content": f"{runtime.context['msgs']['sdlc_context_label']}{context_val}",
|
||||
})
|
||||
return {"messages": messages}
|
||||
|
||||
node_type = "core.append_context_message"
|
||||
category = "core"
|
||||
description = "Append context information to the message list as a system message"
|
||||
|
||||
def execute(self, inputs, runtime=None):
|
||||
"""Append context to the message list."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
context_val = inputs.get("context")
|
||||
if context_val:
|
||||
messages.append({
|
||||
"role": "system",
|
||||
"content": f"{runtime.context['msgs']['sdlc_context_label']}{context_val}",
|
||||
})
|
||||
return {"messages": messages}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Factory for CoreAppendContextMessage plugin."""
|
||||
|
||||
from .core_append_context_message import CoreAppendContextMessage
|
||||
|
||||
|
||||
def create():
|
||||
"""Create a new CoreAppendContextMessage instance."""
|
||||
return CoreAppendContextMessage()
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@metabuilder/core_append_context_message",
|
||||
"version": "1.0.0",
|
||||
"description": "core_append_context_message plugin",
|
||||
"description": "Append context information to the message list as a system message",
|
||||
"author": "MetaBuilder",
|
||||
"license": "MIT",
|
||||
"keywords": ["core", "workflow", "plugin"],
|
||||
"main": "core_append_context_message.py",
|
||||
"files": ["core_append_context_message.py", "factory.py"],
|
||||
"metadata": {
|
||||
"plugin_type": "core.append_context_message",
|
||||
"category": "core"
|
||||
"category": "core",
|
||||
"class": "CoreAppendContextMessage",
|
||||
"entrypoint": "execute"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
"""Workflow plugin: append tool results."""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from ...base import NodeExecutor
|
||||
|
||||
|
||||
def _is_mvp_reached() -> bool:
|
||||
"""Check if the MVP section in ROADMAP.md is completed."""
|
||||
@@ -31,14 +34,21 @@ def _is_mvp_reached() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def run(runtime, inputs):
|
||||
class CoreAppendToolResults(NodeExecutor):
|
||||
"""Append tool results to the message list."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
tool_results = inputs.get("tool_results") or []
|
||||
if tool_results:
|
||||
messages.extend(tool_results)
|
||||
|
||||
if runtime.context.get("args", {}).get("yolo") and _is_mvp_reached():
|
||||
runtime.logger.info("MVP reached. Stopping YOLO loop.")
|
||||
node_type = "core.append_tool_results"
|
||||
category = "core"
|
||||
description = "Append tool execution results to the message list"
|
||||
|
||||
return {"messages": messages}
|
||||
def execute(self, inputs, runtime=None):
|
||||
"""Append tool results to the message list."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
tool_results = inputs.get("tool_results") or []
|
||||
if tool_results:
|
||||
messages.extend(tool_results)
|
||||
|
||||
if runtime.context.get("args", {}).get("yolo") and _is_mvp_reached():
|
||||
runtime.logger.info("MVP reached. Stopping YOLO loop.")
|
||||
|
||||
return {"messages": messages}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Factory for CoreAppendToolResults plugin."""
|
||||
|
||||
from .core_append_tool_results import CoreAppendToolResults
|
||||
|
||||
|
||||
def create():
|
||||
"""Create a new CoreAppendToolResults instance."""
|
||||
return CoreAppendToolResults()
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@metabuilder/core_append_tool_results",
|
||||
"version": "1.0.0",
|
||||
"description": "core_append_tool_results plugin",
|
||||
"description": "Append tool execution results to the message list",
|
||||
"author": "MetaBuilder",
|
||||
"license": "MIT",
|
||||
"keywords": ["core", "workflow", "plugin"],
|
||||
"main": "core_append_tool_results.py",
|
||||
"files": ["core_append_tool_results.py", "factory.py"],
|
||||
"metadata": {
|
||||
"plugin_type": "core.append_tool_results",
|
||||
"category": "core"
|
||||
"category": "core",
|
||||
"class": "CoreAppendToolResults",
|
||||
"entrypoint": "execute"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
"""Workflow plugin: append user instruction."""
|
||||
|
||||
from ...base import NodeExecutor
|
||||
|
||||
def run(runtime, inputs):
|
||||
"""Append the next user instruction."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
messages.append({"role": "user", "content": runtime.context["msgs"]["user_next_step"]})
|
||||
return {"messages": messages}
|
||||
|
||||
class CoreAppendUserInstruction(NodeExecutor):
|
||||
"""Append the next user instruction to the message list."""
|
||||
|
||||
node_type = "core.append_user_instruction"
|
||||
category = "core"
|
||||
description = "Append the next user instruction to the message list"
|
||||
|
||||
def execute(self, inputs, runtime=None):
|
||||
"""Append the next user instruction."""
|
||||
messages = list(inputs.get("messages") or [])
|
||||
messages.append({"role": "user", "content": runtime.context["msgs"]["user_next_step"]})
|
||||
return {"messages": messages}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Factory for CoreAppendUserInstruction plugin."""
|
||||
|
||||
from .core_append_user_instruction import CoreAppendUserInstruction
|
||||
|
||||
|
||||
def create():
|
||||
"""Create a new CoreAppendUserInstruction instance."""
|
||||
return CoreAppendUserInstruction()
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@metabuilder/core_append_user_instruction",
|
||||
"version": "1.0.0",
|
||||
"description": "core_append_user_instruction plugin",
|
||||
"description": "Append the next user instruction to the message list",
|
||||
"author": "MetaBuilder",
|
||||
"license": "MIT",
|
||||
"keywords": ["core", "workflow", "plugin"],
|
||||
"main": "core_append_user_instruction.py",
|
||||
"files": ["core_append_user_instruction.py", "factory.py"],
|
||||
"metadata": {
|
||||
"plugin_type": "core.append_user_instruction",
|
||||
"category": "core"
|
||||
"category": "core",
|
||||
"class": "CoreAppendUserInstruction",
|
||||
"entrypoint": "execute"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,53 @@
|
||||
"""Workflow plugin: load SDLC context."""
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
from ...base import NodeExecutor
|
||||
|
||||
logger = logging.getLogger("metabuilder")
|
||||
|
||||
|
||||
def run(runtime, _inputs):
|
||||
class CoreLoadContext(NodeExecutor):
|
||||
"""Load SDLC context into the workflow store."""
|
||||
gh = runtime.context.get("gh")
|
||||
msgs = runtime.context.get("msgs", {})
|
||||
|
||||
sdlc_context = ""
|
||||
node_type = "core.load_context"
|
||||
category = "core"
|
||||
description = "Load SDLC context from ROADMAP.md and GitHub issues/PRs"
|
||||
|
||||
# Load ROADMAP.md if it exists
|
||||
if os.path.exists("ROADMAP.md"):
|
||||
with open("ROADMAP.md", "r", encoding="utf-8") as f:
|
||||
roadmap_content = f.read()
|
||||
label = msgs.get("roadmap_label", "ROADMAP.md Content:")
|
||||
sdlc_context += f"\n{label}\n{roadmap_content}\n"
|
||||
else:
|
||||
msg = msgs.get(
|
||||
"missing_roadmap_msg",
|
||||
"ROADMAP.md is missing. Please analyze the repository and create it."
|
||||
)
|
||||
sdlc_context += f"\n{msg}\n"
|
||||
def execute(self, inputs, runtime=None):
|
||||
"""Load SDLC context into the workflow store."""
|
||||
gh = runtime.context.get("gh")
|
||||
msgs = runtime.context.get("msgs", {})
|
||||
|
||||
# Load GitHub issues and PRs if integration is available
|
||||
if gh:
|
||||
try:
|
||||
issues = gh.get_open_issues()
|
||||
issue_list = "\n".join([f"- #{i.number}: {i.title}" for i in issues[:5]])
|
||||
if issue_list:
|
||||
sdlc_context += f"\n{msgs['open_issues_label']}\n{issue_list}"
|
||||
sdlc_context = ""
|
||||
|
||||
prs = gh.get_pull_requests()
|
||||
pr_list = "\n".join([f"- #{p.number}: {p.title}" for p in prs[:5]])
|
||||
if pr_list:
|
||||
sdlc_context += f"\n{msgs['open_prs_label']}\n{pr_list}"
|
||||
except Exception as error:
|
||||
logger.error(msgs.get("error_sdlc_context", "Error: {error}").format(error=error))
|
||||
# Load ROADMAP.md if it exists
|
||||
if os.path.exists("ROADMAP.md"):
|
||||
with open("ROADMAP.md", "r", encoding="utf-8") as f:
|
||||
roadmap_content = f.read()
|
||||
label = msgs.get("roadmap_label", "ROADMAP.md Content:")
|
||||
sdlc_context += f"\n{label}\n{roadmap_content}\n"
|
||||
else:
|
||||
msg = msgs.get(
|
||||
"missing_roadmap_msg",
|
||||
"ROADMAP.md is missing. Please analyze the repository and create it."
|
||||
)
|
||||
sdlc_context += f"\n{msg}\n"
|
||||
|
||||
return {"context": sdlc_context}
|
||||
# Load GitHub issues and PRs if integration is available
|
||||
if gh:
|
||||
try:
|
||||
issues = gh.get_open_issues()
|
||||
issue_list = "\n".join([f"- #{i.number}: {i.title}" for i in issues[:5]])
|
||||
if issue_list:
|
||||
sdlc_context += f"\n{msgs['open_issues_label']}\n{issue_list}"
|
||||
|
||||
prs = gh.get_pull_requests()
|
||||
pr_list = "\n".join([f"- #{p.number}: {p.title}" for p in prs[:5]])
|
||||
if pr_list:
|
||||
sdlc_context += f"\n{msgs['open_prs_label']}\n{pr_list}"
|
||||
except Exception as error:
|
||||
logger.error(msgs.get("error_sdlc_context", "Error: {error}").format(error=error))
|
||||
|
||||
return {"context": sdlc_context}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Factory for CoreLoadContext plugin."""
|
||||
|
||||
from .core_load_context import CoreLoadContext
|
||||
|
||||
|
||||
def create():
|
||||
"""Create a new CoreLoadContext instance."""
|
||||
return CoreLoadContext()
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@metabuilder/core_load_context",
|
||||
"version": "1.0.0",
|
||||
"description": "core_load_context plugin",
|
||||
"description": "Load SDLC context from ROADMAP.md and GitHub issues/PRs",
|
||||
"author": "MetaBuilder",
|
||||
"license": "MIT",
|
||||
"keywords": ["core", "workflow", "plugin"],
|
||||
"main": "core_load_context.py",
|
||||
"files": ["core_load_context.py", "factory.py"],
|
||||
"metadata": {
|
||||
"plugin_type": "core.load_context",
|
||||
"category": "core"
|
||||
"category": "core",
|
||||
"class": "CoreLoadContext",
|
||||
"entrypoint": "execute"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
"""Workflow plugin: run tool calls."""
|
||||
|
||||
import json
|
||||
|
||||
def run(runtime, inputs):
|
||||
from ...base import NodeExecutor
|
||||
|
||||
|
||||
class CoreRunToolCalls(NodeExecutor):
|
||||
"""Execute tool calls from an AI response."""
|
||||
resp_msg = inputs.get("response")
|
||||
tool_calls = getattr(resp_msg, "tool_calls", None) or []
|
||||
if not resp_msg:
|
||||
return {"tool_results": [], "no_tool_calls": True}
|
||||
|
||||
# Handle tool calls using tool map from context
|
||||
tool_results = []
|
||||
tool_map = runtime.context.get("tool_map", {})
|
||||
node_type = "core.run_tool_calls"
|
||||
category = "core"
|
||||
description = "Execute tool calls from an AI response and return results"
|
||||
|
||||
for tool_call in tool_calls:
|
||||
func_name = tool_call.function.name
|
||||
if func_name in tool_map:
|
||||
try:
|
||||
import json
|
||||
args = json.loads(tool_call.function.arguments)
|
||||
result = tool_map[func_name](**args)
|
||||
tool_results.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": str(result)
|
||||
})
|
||||
except Exception as e:
|
||||
tool_results.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": f"Error: {str(e)}"
|
||||
})
|
||||
def execute(self, inputs, runtime=None):
|
||||
"""Execute tool calls from an AI response."""
|
||||
resp_msg = inputs.get("response")
|
||||
tool_calls = getattr(resp_msg, "tool_calls", None) or []
|
||||
if not resp_msg:
|
||||
return {"tool_results": [], "no_tool_calls": True}
|
||||
|
||||
return {
|
||||
"tool_results": tool_results,
|
||||
"no_tool_calls": not bool(tool_calls)
|
||||
}
|
||||
# Handle tool calls using tool map from context
|
||||
tool_results = []
|
||||
tool_map = runtime.context.get("tool_map", {})
|
||||
|
||||
for tool_call in tool_calls:
|
||||
func_name = tool_call.function.name
|
||||
if func_name in tool_map:
|
||||
try:
|
||||
args = json.loads(tool_call.function.arguments)
|
||||
result = tool_map[func_name](**args)
|
||||
tool_results.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": str(result)
|
||||
})
|
||||
except Exception as e:
|
||||
tool_results.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": f"Error: {str(e)}"
|
||||
})
|
||||
|
||||
return {
|
||||
"tool_results": tool_results,
|
||||
"no_tool_calls": not bool(tool_calls)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Factory for CoreRunToolCalls plugin."""
|
||||
|
||||
from .core_run_tool_calls import CoreRunToolCalls
|
||||
|
||||
|
||||
def create():
|
||||
"""Create a new CoreRunToolCalls instance."""
|
||||
return CoreRunToolCalls()
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@metabuilder/core_run_tool_calls",
|
||||
"version": "1.0.0",
|
||||
"description": "core_run_tool_calls plugin",
|
||||
"description": "Execute tool calls from an AI response and return results",
|
||||
"author": "MetaBuilder",
|
||||
"license": "MIT",
|
||||
"keywords": ["core", "workflow", "plugin"],
|
||||
"main": "core_run_tool_calls.py",
|
||||
"files": ["core_run_tool_calls.py", "factory.py"],
|
||||
"metadata": {
|
||||
"plugin_type": "core.run_tool_calls",
|
||||
"category": "core"
|
||||
"category": "core",
|
||||
"class": "CoreRunToolCalls",
|
||||
"entrypoint": "execute"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
"""Workflow plugin: seed messages."""
|
||||
|
||||
from ...base import NodeExecutor
|
||||
|
||||
def run(runtime, _inputs):
|
||||
|
||||
class CoreSeedMessages(NodeExecutor):
|
||||
"""Seed messages from the prompt."""
|
||||
prompt = runtime.context["prompt"]
|
||||
return {"messages": list(prompt["messages"])}
|
||||
|
||||
node_type = "core.seed_messages"
|
||||
category = "core"
|
||||
description = "Initialize the message list from the prompt configuration"
|
||||
|
||||
def execute(self, inputs, runtime=None):
|
||||
"""Seed messages from the prompt."""
|
||||
prompt = runtime.context["prompt"]
|
||||
return {"messages": list(prompt["messages"])}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
"""Factory for CoreSeedMessages plugin."""
|
||||
|
||||
from .core_seed_messages import CoreSeedMessages
|
||||
|
||||
|
||||
def create():
|
||||
"""Create a new CoreSeedMessages instance."""
|
||||
return CoreSeedMessages()
|
||||
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "@metabuilder/core_seed_messages",
|
||||
"version": "1.0.0",
|
||||
"description": "core_seed_messages plugin",
|
||||
"description": "Initialize the message list from the prompt configuration",
|
||||
"author": "MetaBuilder",
|
||||
"license": "MIT",
|
||||
"keywords": ["core", "workflow", "plugin"],
|
||||
"main": "core_seed_messages.py",
|
||||
"files": ["core_seed_messages.py", "factory.py"],
|
||||
"metadata": {
|
||||
"plugin_type": "core.seed_messages",
|
||||
"category": "core"
|
||||
"category": "core",
|
||||
"class": "CoreSeedMessages",
|
||||
"entrypoint": "execute"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user