autobots-devtools-shared-lib 0.8.0__tar.gz → 0.9.0__tar.gz
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.
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/PKG-INFO +1 -1
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/pyproject.toml +1 -1
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/noderedmanagerserver/app.py +6 -4
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/noderedmanagerserver/models.py +1 -0
- autobots_devtools_shared_lib-0.9.0/src/autobots_devtools_shared_lib/common/tools/noderedmanager_client_tools.py +134 -0
- autobots_devtools_shared_lib-0.9.0/src/autobots_devtools_shared_lib/common/utils/noderedmanager_client_utils.py +220 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/README.md +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/config/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/config/jenkins_config.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/config/jenkins_constants.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/config/jenkins_loader.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/observability/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/observability/logging_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/observability/otel_fastapi.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/observability/trace_metadata.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/observability/trace_propagation.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/observability/tracing.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/fileserver/README.md +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/fileserver/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/fileserver/app.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/fileserver/config.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/fileserver/models.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/noderedmanagerserver/README.md +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/noderedmanagerserver/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/noderedmanagerserver/__main__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/servers/noderedmanagerserver/config.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/README.md +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/cache_backed.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/db_repository.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/factory.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/in_memory.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/redis_store.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/services/context/store.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/tools/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/tools/context_tools.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/tools/format_tools.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/tools/fserver_client_tools.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/tools/jenkins_builtin_tools.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/tools/jenkins_pipeline_tools.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/utils/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/utils/context_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/utils/format_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/utils/fserver_client_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/utils/jenkins_builtin_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/utils/jenkins_http_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/utils/jenkins_pipeline_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/agents/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/agents/agent_config_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/agents/agent_meta.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/agents/base_agent.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/agents/batch.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/agents/invocation_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/agents/middleware.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/config/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/config/dynagent_settings.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/llm/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/llm/llm.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/models/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/models/state.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/services/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/services/structured_converter.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/tools/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/tools/state_tools.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/tools/tool_registry.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/ui/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/ui/default_ui.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/ui/ui_utils.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/utils/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/utils/schema_directive_resolver.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/assertions/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/assertions/deterministic.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/assertions/golden.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/assertions/llm_judge.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/assertions/registry.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/assertions/written_file.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/core/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/core/cost_tracker.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/core/loader.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/core/runner.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/core/workspace.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/models/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/models/eval_case.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/models/result.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/pytest_plugin/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/pytest_plugin/fixtures.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/pytest_plugin/plugin.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/pytest_plugin/reporting.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/scoring/__init__.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/scoring/langfuse_scorer.py +0 -0
- {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/py.typed +0 -0
|
@@ -227,7 +227,7 @@ async def create_instance(body: CreateInstanceRequest) -> CreateInstanceResponse
|
|
|
227
227
|
body.workspace_context,
|
|
228
228
|
)
|
|
229
229
|
|
|
230
|
-
# 1. Extract and validate workspace_base_path
|
|
230
|
+
# 1. Extract and validate workspace_base_path
|
|
231
231
|
workspace_base_path: str = (body.workspace_context.get("workspace_base_path") or "").strip()
|
|
232
232
|
if not workspace_base_path:
|
|
233
233
|
raise HTTPException(
|
|
@@ -239,7 +239,8 @@ async def create_instance(body: CreateInstanceRequest) -> CreateInstanceResponse
|
|
|
239
239
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
240
240
|
detail="workspace_base_path cannot contain '..'",
|
|
241
241
|
)
|
|
242
|
-
|
|
242
|
+
# Instance ID scoped per environment so the same workspace can run multiple environments
|
|
243
|
+
instance_id = f"{body.environment_name}/{workspace_base_path}"
|
|
243
244
|
|
|
244
245
|
# 2. Return existing instance if one is already running for this workspace
|
|
245
246
|
if instance_id in _registry:
|
|
@@ -302,12 +303,13 @@ async def create_instance(body: CreateInstanceRequest) -> CreateInstanceResponse
|
|
|
302
303
|
@app.post("/kill-instance")
|
|
303
304
|
async def kill_instance(body: KillInstanceRequest) -> KillInstanceResponse:
|
|
304
305
|
"""Kill a running Node-RED instance by workspace_base_path."""
|
|
305
|
-
|
|
306
|
-
if not
|
|
306
|
+
workspace_base_path: str = (body.workspace_context.get("workspace_base_path") or "").strip()
|
|
307
|
+
if not workspace_base_path:
|
|
307
308
|
raise HTTPException(
|
|
308
309
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
309
310
|
detail="workspace_context.workspace_base_path is required and cannot be empty.",
|
|
310
311
|
)
|
|
312
|
+
instance_id = f"{body.environment_name}/{workspace_base_path}"
|
|
311
313
|
logger.info("kill-instance called id=%s", instance_id)
|
|
312
314
|
|
|
313
315
|
entry = _registry.get(instance_id)
|
|
@@ -49,6 +49,7 @@ class KillInstanceRequest(BaseModel):
|
|
|
49
49
|
description="Workspace scoping context. Must include `workspace_base_path`.",
|
|
50
50
|
json_schema_extra={"examples": [{"workspace_base_path": "alice/my-project-JIRA-42"}]},
|
|
51
51
|
)
|
|
52
|
+
environment_name: str = Field(..., description="Name of the Node-RED environment to kill.")
|
|
52
53
|
|
|
53
54
|
|
|
54
55
|
class KillInstanceResponse(BaseModel):
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""LangChain tools wrapping Node-RED instance manager REST API endpoints."""
|
|
2
|
+
|
|
3
|
+
from langchain.tools import ToolException, ToolRuntime, tool
|
|
4
|
+
|
|
5
|
+
from autobots_devtools_shared_lib.common.observability.logging_utils import get_logger
|
|
6
|
+
from autobots_devtools_shared_lib.common.utils.noderedmanager_client_utils import (
|
|
7
|
+
create_instance,
|
|
8
|
+
get_health,
|
|
9
|
+
kill_instance,
|
|
10
|
+
list_instances,
|
|
11
|
+
)
|
|
12
|
+
from autobots_devtools_shared_lib.dynagent.models.state import Dynagent
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _session_id_from_runtime(runtime: ToolRuntime[None, Dynagent] | None) -> str | None:
|
|
18
|
+
"""Extract session_id from runtime state if available."""
|
|
19
|
+
if runtime is None:
|
|
20
|
+
return None
|
|
21
|
+
state = runtime.state
|
|
22
|
+
if state is None:
|
|
23
|
+
return None
|
|
24
|
+
session_id = state.get("session_id")
|
|
25
|
+
if not isinstance(session_id, str) or not session_id:
|
|
26
|
+
return None
|
|
27
|
+
return session_id
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _check_result(result: str, operation: str) -> None:
|
|
31
|
+
"""If result is an error message, log and raise ToolException."""
|
|
32
|
+
if result.strip().startswith("Error "):
|
|
33
|
+
logger.warning("[tools] %s failed: %s", operation, result)
|
|
34
|
+
raise ToolException(result)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@tool
|
|
38
|
+
def get_health_tool(runtime: ToolRuntime[None, Dynagent] | None = None) -> str:
|
|
39
|
+
"""Get the health status of the Node-RED instance manager server."""
|
|
40
|
+
logger.info("[tools] Getting Node-RED manager health")
|
|
41
|
+
try:
|
|
42
|
+
result = get_health(session_id=_session_id_from_runtime(runtime))
|
|
43
|
+
_check_result(result, "get_health")
|
|
44
|
+
except ToolException:
|
|
45
|
+
raise
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.exception("[tools] get_health_tool failed")
|
|
48
|
+
raise ToolException(f"Error getting health: {e!s}") from e
|
|
49
|
+
else:
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@tool
|
|
54
|
+
def list_instances_tool(runtime: ToolRuntime[None, Dynagent] | None = None) -> str:
|
|
55
|
+
"""List all currently running Node-RED instances."""
|
|
56
|
+
logger.info("[tools] Listing Node-RED instances")
|
|
57
|
+
try:
|
|
58
|
+
result = list_instances(session_id=_session_id_from_runtime(runtime))
|
|
59
|
+
_check_result(result, "list_instances")
|
|
60
|
+
except ToolException:
|
|
61
|
+
raise
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.exception("[tools] list_instances_tool failed")
|
|
64
|
+
raise ToolException(f"Error listing instances: {e!s}") from e
|
|
65
|
+
else:
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@tool
|
|
70
|
+
def create_instance_tool(
|
|
71
|
+
runtime: ToolRuntime[None, Dynagent],
|
|
72
|
+
workspace_base_path: str = "",
|
|
73
|
+
flows_json_path: str = "",
|
|
74
|
+
environment_name: str = "",
|
|
75
|
+
) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Launch a new Node-RED instance for the given workspace.
|
|
78
|
+
|
|
79
|
+
Returns the instance id and URL. If an instance already exists for this workspace,
|
|
80
|
+
the existing one is returned without launching a new process.
|
|
81
|
+
"""
|
|
82
|
+
logger.info(
|
|
83
|
+
"[tools] Creating Node-RED instance workspace=%r flows=%r environment=%r",
|
|
84
|
+
workspace_base_path,
|
|
85
|
+
flows_json_path,
|
|
86
|
+
environment_name,
|
|
87
|
+
)
|
|
88
|
+
try:
|
|
89
|
+
result = create_instance(
|
|
90
|
+
workspace_base_path,
|
|
91
|
+
flows_json_path,
|
|
92
|
+
environment_name,
|
|
93
|
+
session_id=_session_id_from_runtime(runtime),
|
|
94
|
+
)
|
|
95
|
+
_check_result(result, "create_instance")
|
|
96
|
+
except ToolException:
|
|
97
|
+
raise
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.exception("[tools] create_instance_tool failed workspace=%r", workspace_base_path)
|
|
100
|
+
raise ToolException(f"Error creating instance: {e!s}") from e
|
|
101
|
+
else:
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@tool
|
|
106
|
+
def kill_instance_tool(
|
|
107
|
+
runtime: ToolRuntime[None, Dynagent],
|
|
108
|
+
workspace_base_path: str = "",
|
|
109
|
+
environment_name: str = "",
|
|
110
|
+
) -> str:
|
|
111
|
+
"""Kill the running Node-RED instance for the given workspace and environment."""
|
|
112
|
+
logger.info(
|
|
113
|
+
"[tools] Killing Node-RED instance workspace=%r environment=%r",
|
|
114
|
+
workspace_base_path,
|
|
115
|
+
environment_name,
|
|
116
|
+
)
|
|
117
|
+
try:
|
|
118
|
+
result = kill_instance(
|
|
119
|
+
workspace_base_path,
|
|
120
|
+
environment_name,
|
|
121
|
+
session_id=_session_id_from_runtime(runtime),
|
|
122
|
+
)
|
|
123
|
+
_check_result(result, "kill_instance")
|
|
124
|
+
except ToolException:
|
|
125
|
+
raise
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.exception(
|
|
128
|
+
"[tools] kill_instance_tool failed workspace=%r environment=%r",
|
|
129
|
+
workspace_base_path,
|
|
130
|
+
environment_name,
|
|
131
|
+
)
|
|
132
|
+
raise ToolException(f"Error killing instance: {e!s}") from e
|
|
133
|
+
else:
|
|
134
|
+
return result
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""HTTP client utilities for the Node-RED instance manager server REST API."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from autobots_devtools_shared_lib.common.observability.logging_utils import get_logger
|
|
8
|
+
from autobots_devtools_shared_lib.common.observability.trace_propagation import traced_http_call
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
NODE_RED_MANAGER_HOST = os.getenv("NODE_RED_MANAGER_HOST", "localhost")
|
|
13
|
+
NODE_RED_MANAGER_PORT = os.getenv("NODE_RED_MANAGER_PORT", "9003")
|
|
14
|
+
NODE_RED_MANAGER_BASE_URL = f"http://{NODE_RED_MANAGER_HOST}:{NODE_RED_MANAGER_PORT}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_health(session_id: str | None = None) -> str:
|
|
18
|
+
"""
|
|
19
|
+
Get the health status of the Node-RED manager server.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
session_id: Optional session ID for trace correlation.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
String representation of health info (status, running_instances, available_environments).
|
|
26
|
+
"""
|
|
27
|
+
logger.info("Getting Node-RED manager health")
|
|
28
|
+
try:
|
|
29
|
+
with (
|
|
30
|
+
traced_http_call("noderedManagerGetHealth", session_id=session_id) as trace_headers,
|
|
31
|
+
httpx.Client() as client,
|
|
32
|
+
):
|
|
33
|
+
response = client.get(
|
|
34
|
+
f"{NODE_RED_MANAGER_BASE_URL}/health",
|
|
35
|
+
headers=trace_headers,
|
|
36
|
+
timeout=30.0,
|
|
37
|
+
)
|
|
38
|
+
response.raise_for_status()
|
|
39
|
+
result = response.json()
|
|
40
|
+
except httpx.HTTPStatusError as e:
|
|
41
|
+
logger.exception(
|
|
42
|
+
"HTTP error getting Node-RED manager health: %s - %s",
|
|
43
|
+
e.response.status_code,
|
|
44
|
+
e.response.text,
|
|
45
|
+
)
|
|
46
|
+
return f"Error getting health: HTTP {e.response.status_code} - {e.response.text}"
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.exception("Error getting Node-RED manager health")
|
|
49
|
+
return f"Error getting health: {e!s}"
|
|
50
|
+
else:
|
|
51
|
+
logger.info("Node-RED manager health: %s", result.get("status"))
|
|
52
|
+
return str(result)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def list_instances(session_id: str | None = None) -> str:
|
|
56
|
+
"""
|
|
57
|
+
List all currently running Node-RED instances.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
session_id: Optional session ID for trace correlation.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
String representation of the instances list with count.
|
|
64
|
+
"""
|
|
65
|
+
logger.info("Listing Node-RED instances")
|
|
66
|
+
try:
|
|
67
|
+
with (
|
|
68
|
+
traced_http_call("noderedManagerListInstances", session_id=session_id) as trace_headers,
|
|
69
|
+
httpx.Client() as client,
|
|
70
|
+
):
|
|
71
|
+
response = client.get(
|
|
72
|
+
f"{NODE_RED_MANAGER_BASE_URL}/instances",
|
|
73
|
+
headers=trace_headers,
|
|
74
|
+
timeout=30.0,
|
|
75
|
+
)
|
|
76
|
+
response.raise_for_status()
|
|
77
|
+
result = response.json()
|
|
78
|
+
except httpx.HTTPStatusError as e:
|
|
79
|
+
logger.exception(
|
|
80
|
+
"HTTP error listing Node-RED instances: %s - %s",
|
|
81
|
+
e.response.status_code,
|
|
82
|
+
e.response.text,
|
|
83
|
+
)
|
|
84
|
+
return f"Error listing instances: HTTP {e.response.status_code} - {e.response.text}"
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.exception("Error listing Node-RED instances")
|
|
87
|
+
return f"Error listing instances: {e!s}"
|
|
88
|
+
else:
|
|
89
|
+
instances = result.get("instances", [])
|
|
90
|
+
logger.info("Listed %d Node-RED instance(s)", len(instances))
|
|
91
|
+
return str(result)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def create_instance(
|
|
95
|
+
workspace_base_path: str,
|
|
96
|
+
flows_json_path: str,
|
|
97
|
+
environment_name: str,
|
|
98
|
+
session_id: str | None = None,
|
|
99
|
+
) -> str:
|
|
100
|
+
"""
|
|
101
|
+
Launch a new Node-RED instance (or return an existing one for the same workspace+environment).
|
|
102
|
+
|
|
103
|
+
The instance ID is scoped as ``environment_name/workspace_base_path`` so the same workspace
|
|
104
|
+
can run multiple environments simultaneously.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
workspace_base_path: Workspace path (e.g. 'user/repo-JIRA-42').
|
|
108
|
+
flows_json_path: Relative path to flows.json within the workspace directory.
|
|
109
|
+
environment_name: Name of the Node-RED environment template to use.
|
|
110
|
+
session_id: Optional session ID for trace correlation.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Success message with instance id and url.
|
|
114
|
+
"""
|
|
115
|
+
logger.info(
|
|
116
|
+
"Creating Node-RED instance workspace=%r flows=%r environment=%r",
|
|
117
|
+
workspace_base_path,
|
|
118
|
+
flows_json_path,
|
|
119
|
+
environment_name,
|
|
120
|
+
)
|
|
121
|
+
try:
|
|
122
|
+
payload = {
|
|
123
|
+
"workspace_context": {"workspace_base_path": workspace_base_path},
|
|
124
|
+
"flows_json_path": flows_json_path,
|
|
125
|
+
"environment_name": environment_name,
|
|
126
|
+
}
|
|
127
|
+
with (
|
|
128
|
+
traced_http_call(
|
|
129
|
+
"noderedManagerCreateInstance", session_id=session_id
|
|
130
|
+
) as trace_headers,
|
|
131
|
+
httpx.Client() as client,
|
|
132
|
+
):
|
|
133
|
+
response = client.post(
|
|
134
|
+
f"{NODE_RED_MANAGER_BASE_URL}/create-instance",
|
|
135
|
+
json=payload,
|
|
136
|
+
headers=trace_headers,
|
|
137
|
+
timeout=30.0,
|
|
138
|
+
)
|
|
139
|
+
response.raise_for_status()
|
|
140
|
+
result = response.json()
|
|
141
|
+
except httpx.HTTPStatusError as e:
|
|
142
|
+
logger.exception(
|
|
143
|
+
"HTTP error creating Node-RED instance workspace=%r: %s - %s",
|
|
144
|
+
workspace_base_path,
|
|
145
|
+
e.response.status_code,
|
|
146
|
+
e.response.text,
|
|
147
|
+
)
|
|
148
|
+
return f"Error creating instance: HTTP {e.response.status_code} - {e.response.text}"
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.exception("Error creating Node-RED instance workspace=%r", workspace_base_path)
|
|
151
|
+
return f"Error creating instance: {e!s}"
|
|
152
|
+
else:
|
|
153
|
+
logger.info(
|
|
154
|
+
"Node-RED instance created/reused: id=%s url=%s", result.get("id"), result.get("url")
|
|
155
|
+
)
|
|
156
|
+
return f"Instance created: id={result['id']} url={result['url']}"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def kill_instance(
|
|
160
|
+
workspace_base_path: str,
|
|
161
|
+
environment_name: str,
|
|
162
|
+
session_id: str | None = None,
|
|
163
|
+
) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Kill a running Node-RED instance by workspace_base_path and environment_name.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
workspace_base_path: Workspace path identifying the instance to kill.
|
|
169
|
+
environment_name: Environment name of the instance to kill.
|
|
170
|
+
session_id: Optional session ID for trace correlation.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Success message from the server.
|
|
174
|
+
"""
|
|
175
|
+
logger.info(
|
|
176
|
+
"Killing Node-RED instance workspace=%r environment=%r",
|
|
177
|
+
workspace_base_path,
|
|
178
|
+
environment_name,
|
|
179
|
+
)
|
|
180
|
+
try:
|
|
181
|
+
payload = {
|
|
182
|
+
"workspace_context": {"workspace_base_path": workspace_base_path},
|
|
183
|
+
"environment_name": environment_name,
|
|
184
|
+
}
|
|
185
|
+
with (
|
|
186
|
+
traced_http_call("noderedManagerKillInstance", session_id=session_id) as trace_headers,
|
|
187
|
+
httpx.Client() as client,
|
|
188
|
+
):
|
|
189
|
+
response = client.post(
|
|
190
|
+
f"{NODE_RED_MANAGER_BASE_URL}/kill-instance",
|
|
191
|
+
json=payload,
|
|
192
|
+
headers=trace_headers,
|
|
193
|
+
timeout=30.0,
|
|
194
|
+
)
|
|
195
|
+
response.raise_for_status()
|
|
196
|
+
result = response.json()
|
|
197
|
+
except httpx.HTTPStatusError as e:
|
|
198
|
+
logger.exception(
|
|
199
|
+
"HTTP error killing Node-RED instance workspace=%r: %s - %s",
|
|
200
|
+
workspace_base_path,
|
|
201
|
+
e.response.status_code,
|
|
202
|
+
e.response.text,
|
|
203
|
+
)
|
|
204
|
+
return f"Error killing instance: HTTP {e.response.status_code} - {e.response.text}"
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.exception(
|
|
207
|
+
"Error killing Node-RED instance workspace=%r environment=%r",
|
|
208
|
+
workspace_base_path,
|
|
209
|
+
environment_name,
|
|
210
|
+
)
|
|
211
|
+
return f"Error killing instance: {e!s}"
|
|
212
|
+
else:
|
|
213
|
+
message = result.get("message", "Instance killed successfully")
|
|
214
|
+
logger.info(
|
|
215
|
+
"Node-RED instance killed workspace=%r environment=%r: %s",
|
|
216
|
+
workspace_base_path,
|
|
217
|
+
environment_name,
|
|
218
|
+
message,
|
|
219
|
+
)
|
|
220
|
+
return message
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|