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.
Files changed (97) hide show
  1. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/PKG-INFO +1 -1
  2. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/pyproject.toml +1 -1
  3. {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
  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
  5. autobots_devtools_shared_lib-0.9.0/src/autobots_devtools_shared_lib/common/tools/noderedmanager_client_tools.py +134 -0
  6. autobots_devtools_shared_lib-0.9.0/src/autobots_devtools_shared_lib/common/utils/noderedmanager_client_utils.py +220 -0
  7. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/README.md +0 -0
  8. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/__init__.py +0 -0
  9. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/common/__init__.py +0 -0
  10. {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
  11. {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
  12. {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
  13. {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
  14. {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
  15. {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
  16. {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
  17. {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
  18. {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
  19. {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
  20. {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
  21. {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
  22. {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
  23. {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
  24. {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
  25. {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
  26. {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
  27. {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
  28. {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
  29. {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
  30. {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
  31. {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
  32. {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
  33. {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
  34. {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
  35. {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
  36. {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
  37. {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
  38. {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
  39. {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
  40. {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
  41. {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
  42. {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
  43. {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
  44. {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
  45. {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
  46. {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
  47. {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
  48. {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
  49. {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
  50. {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
  51. {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
  52. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/dynagent/__init__.py +0 -0
  53. {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
  54. {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
  55. {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
  56. {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
  57. {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
  58. {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
  59. {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
  60. {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
  61. {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
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {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
  70. {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
  71. {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
  72. {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
  73. {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
  74. {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
  75. {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
  76. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/eval/__init__.py +0 -0
  77. {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
  78. {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
  79. {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
  80. {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
  81. {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
  82. {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
  83. {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
  84. {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
  85. {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
  86. {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
  87. {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
  88. {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
  89. {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
  90. {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
  91. {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
  92. {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
  93. {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
  94. {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
  95. {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
  96. {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
  97. {autobots_devtools_shared_lib-0.8.0 → autobots_devtools_shared_lib-0.9.0}/src/autobots_devtools_shared_lib/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autobots-devtools-shared-lib
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Shared library functions to be used for all autobots projects
5
5
  License: MIT
6
6
  Author: Pralhad
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "autobots-devtools-shared-lib"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "Shared library functions to be used for all autobots projects"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -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 — used as the instance ID
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
- instance_id = workspace_base_path
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
- instance_id: str = (body.workspace_context.get("workspace_base_path") or "").strip()
306
- if not instance_id:
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