Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README-nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ channel; they have no data lanes and **bind to an agent** (see
| `tool_bland_ai` | Places and manages AI phone calls via Bland AI |
| `tool_slack` | Slack workspace operations: post messages, list channels, read history |
| `tool_oura` | Oura Ring health data (sleep, readiness, activity, heart rate), read-only |
| `tool_insforge` | InsForge backend: record CRUD, RPC calls, storage; read-only by default |
| `tool_xtrace_memory`| Long-term shared agent memory, backed by xTrace Memory Manager |
| `tool_mem0` | Long-term shared agent memory, backed by the hosted Mem0 Platform |

Expand Down
126 changes: 126 additions & 0 deletions nodes/src/nodes/tool_insforge/IGlobal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# =============================================================================
# RocketRide Engine
# =============================================================================
# MIT License
# Copyright (c) 2026 Aparavi Software AG
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# =============================================================================

"""
InsForge tool node - global (shared) state.

Resolves the InsForge project URL and API credential from node config,
connection config, or the ROCKETRIDE_INSFORGE_URL / ROCKETRIDE_INSFORGE_KEY
environment variables, and records whether writes are permitted.
"""

from __future__ import annotations

import os

from ai.common.config import Config
from ai.common.utils import parse_bool
from rocketlib import IGlobalBase, OPEN_MODE, warning

from .insforge_client import normalize_base_url


class IGlobal(IGlobalBase):
"""Global state for tool_insforge."""

base_url: str = ''
token: str = ''
allow_writes: bool = False

@staticmethod
def _get(cfg: dict, conn_config: dict, key: str, env_var: str) -> str:
"""Resolve one setting from node config, connection config, then env."""
value = str((cfg.get(key) or '')).strip()
if value:
return value
value = str((conn_config.get(key) or '')).strip()
if value:
return value
return str((os.environ.get(env_var) or '')).strip()

def _resolve(self, cfg: dict, conn_config: dict) -> tuple[str, str]:
"""Return the (project_url, api_key) pair from all configured sources."""
return (
self._get(cfg, conn_config, 'project_url', 'ROCKETRIDE_INSFORGE_URL'),
self._get(cfg, conn_config, 'api_key', 'ROCKETRIDE_INSFORGE_KEY'),
)

def beginGlobal(self) -> None:
"""Validate credentials and install dependencies for execution mode.

Raises:
Exception: If the project URL or API key is missing, or the URL is
malformed, so the pipeline fails early with a node-specific
message rather than on the first tool call.
"""
if self.IEndpoint.endpoint.openMode == OPEN_MODE.CONFIG:
return

from depends import load_depends

load_depends(__file__)

cfg = Config.getNodeConfig(self.glb.logicalType, self.glb.connConfig)
project_url, api_key = self._resolve(cfg, self.glb.connConfig)

if not project_url:
raise Exception(
'tool_insforge: project_url is required (set it in the node config or ROCKETRIDE_INSFORGE_URL)'
)
if not api_key:
raise Exception('tool_insforge: api_key is required (set it in the node config or ROCKETRIDE_INSFORGE_KEY)')

try:
self.base_url = normalize_base_url(project_url)
except ValueError as e:
raise Exception(f'tool_insforge: {e}') from e

self.token = api_key
self.allow_writes = parse_bool(cfg.get('allow_writes'))

def validateConfig(self) -> None:
"""Surface missing or malformed settings in the editor without connecting."""
try:
cfg = Config.getNodeConfig(self.glb.logicalType, self.glb.connConfig)
project_url, api_key = self._resolve(cfg, self.glb.connConfig)

if not project_url:
warning('project_url is required (or set ROCKETRIDE_INSFORGE_URL)')
else:
try:
normalize_base_url(project_url)
except ValueError as e:
warning(str(e))

if not api_key:
warning('api_key is required (or set ROCKETRIDE_INSFORGE_KEY)')
except Exception as e:
warning(str(e))

def endGlobal(self) -> None:
"""Release shared state."""
self.base_url = ''
self.token = ''
self.allow_writes = False
Loading
Loading