mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-26 01:01:40 +00:00
Add inference.sh CLI (infsh) as a tool integration, giving agents access to 150+ AI apps through a single CLI — image gen (FLUX, Reve, Seedream), video (Veo, Wan, Seedance), LLMs, search (Tavily, Exa), 3D, avatar/lipsync, and more. One API key manages all services. Tools: - infsh: run any infsh CLI command (app list, app run, etc.) - infsh_install: install the CLI if not present Registered as an 'inference' toolset (opt-in, not in core tools). Includes comprehensive skill docs with examples for all app categories. Changes from original PR: - NOT added to _HERMES_CORE_TOOLS (available via --toolsets inference) - Added 12 tests covering tool registration, command execution, error handling, timeout, JSON parsing, and install flow Inspired by PR #1021 by @okaris. Co-authored-by: okaris <okaris@users.noreply.github.com>
114 lines
4.4 KiB
Python
114 lines
4.4 KiB
Python
"""Tests for tools/infsh_tool.py — inference.sh CLI integration."""
|
|
|
|
import json
|
|
import subprocess
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import pytest
|
|
|
|
from tools.infsh_tool import (
|
|
check_infsh_requirements,
|
|
infsh_tool,
|
|
infsh_install,
|
|
)
|
|
|
|
|
|
class TestCheckRequirements:
|
|
def test_returns_bool(self):
|
|
result = check_infsh_requirements()
|
|
assert isinstance(result, bool)
|
|
|
|
def test_returns_true_when_infsh_on_path(self, monkeypatch):
|
|
monkeypatch.setattr("shutil.which", lambda cmd: "/usr/local/bin/infsh" if cmd == "infsh" else None)
|
|
assert check_infsh_requirements() is True
|
|
|
|
def test_returns_false_when_missing(self, monkeypatch):
|
|
monkeypatch.setattr("shutil.which", lambda cmd: None)
|
|
assert check_infsh_requirements() is False
|
|
|
|
|
|
class TestInfshTool:
|
|
def test_not_installed_returns_error(self, monkeypatch):
|
|
monkeypatch.setattr("tools.infsh_tool.check_infsh_requirements", lambda: False)
|
|
result = json.loads(infsh_tool("app list"))
|
|
assert result["success"] is False
|
|
assert "not installed" in result["error"].lower()
|
|
|
|
def test_successful_command(self, monkeypatch):
|
|
monkeypatch.setattr("tools.infsh_tool.check_infsh_requirements", lambda: True)
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = '{"apps": ["flux", "veo"]}'
|
|
mock_result.stderr = ""
|
|
|
|
with patch("subprocess.run", return_value=mock_result) as mock_run:
|
|
result = json.loads(infsh_tool("app list --search flux"))
|
|
assert result["success"] is True
|
|
mock_run.assert_called_once()
|
|
call_cmd = mock_run.call_args[0][0]
|
|
assert "infsh app list --search flux" in call_cmd
|
|
|
|
def test_failed_command(self, monkeypatch):
|
|
monkeypatch.setattr("tools.infsh_tool.check_infsh_requirements", lambda: True)
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 1
|
|
mock_result.stdout = ""
|
|
mock_result.stderr = "unknown command"
|
|
|
|
with patch("subprocess.run", return_value=mock_result):
|
|
result = json.loads(infsh_tool("badcommand"))
|
|
assert result["success"] is False
|
|
assert result["exit_code"] == 1
|
|
|
|
def test_timeout_handled(self, monkeypatch):
|
|
monkeypatch.setattr("tools.infsh_tool.check_infsh_requirements", lambda: True)
|
|
|
|
with patch("subprocess.run", side_effect=subprocess.TimeoutExpired("infsh", 300)):
|
|
result = json.loads(infsh_tool("app run something", timeout=300))
|
|
assert result["success"] is False
|
|
assert "timed out" in result["error"].lower()
|
|
|
|
def test_json_output_parsed(self, monkeypatch):
|
|
monkeypatch.setattr("tools.infsh_tool.check_infsh_requirements", lambda: True)
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = '{"url": "https://example.com/image.png"}'
|
|
mock_result.stderr = ""
|
|
|
|
with patch("subprocess.run", return_value=mock_result):
|
|
result = json.loads(infsh_tool("app run flux --json"))
|
|
assert result["success"] is True
|
|
assert isinstance(result["output"], dict)
|
|
assert result["output"]["url"] == "https://example.com/image.png"
|
|
|
|
|
|
class TestInfshInstall:
|
|
def test_already_installed(self, monkeypatch):
|
|
monkeypatch.setattr("tools.infsh_tool.check_infsh_requirements", lambda: True)
|
|
monkeypatch.setattr("tools.infsh_tool._get_infsh_path", lambda: "/usr/local/bin/infsh")
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = "infsh v1.2.3"
|
|
|
|
with patch("subprocess.run", return_value=mock_result):
|
|
result = json.loads(infsh_install())
|
|
assert result["success"] is True
|
|
assert result["already_installed"] is True
|
|
|
|
|
|
class TestToolRegistration:
|
|
def test_tools_registered(self):
|
|
from tools.registry import registry
|
|
assert "infsh" in registry._tools
|
|
assert "infsh_install" in registry._tools
|
|
|
|
def test_infsh_in_inference_toolset(self):
|
|
from toolsets import TOOLSETS
|
|
assert "inference" in TOOLSETS
|
|
assert "infsh" in TOOLSETS["inference"]["tools"]
|
|
assert "infsh_install" in TOOLSETS["inference"]["tools"]
|
|
|
|
def test_infsh_not_in_core_tools(self):
|
|
from toolsets import _HERMES_CORE_TOOLS
|
|
assert "infsh" not in _HERMES_CORE_TOOLS
|
|
assert "infsh_install" not in _HERMES_CORE_TOOLS
|