Add SaaS AI engine (#5907)

This commit is contained in:
James Brunton
2026-03-16 11:01:50 +00:00
committed by GitHub
parent cddc8e6df0
commit c58a6092ec
182 changed files with 29961 additions and 3 deletions

11
engine/tests/conftest.py Normal file
View File

@@ -0,0 +1,11 @@
from __future__ import annotations
import os
from pytest import Config
def pytest_configure(config: Config) -> None:
# Set required env vars in case there is no .env file
os.environ.setdefault("STIRLING_OPENAI_API_KEY", "test")
os.environ.setdefault("STIRLING_POSTHOG_API_KEY", "test")

View File

@@ -0,0 +1,160 @@
from __future__ import annotations
from pytest import MonkeyPatch
from chat_router import classify_chat_route
from models import (
ChatRouteRequest,
ChatRouteResponse,
CreateIntentHint,
EditIntentHint,
SmartFolderIntentHint,
)
def test_classify_chat_route_handles_smart_folder_intent(monkeypatch: MonkeyPatch):
request = ChatRouteRequest(
message="Create a workflow that batches PDFs overnight",
history=[],
has_files=False,
has_create_session=False,
has_edit_session=False,
last_route="none",
)
expected_response = ChatRouteResponse(
intent="smart_folder",
smart_folder_intent=SmartFolderIntentHint(action="create"),
reason="User wants to automate PDFs",
)
with monkeypatch.context() as m:
m.setattr("chat_router.run_ai", lambda *args, **kwargs: expected_response)
response = classify_chat_route(request)
assert response.intent == "smart_folder"
assert response.smart_folder_intent == expected_response.smart_folder_intent
def test_greeting_without_files(monkeypatch: MonkeyPatch):
"""Greetings should route to edit/info, not create"""
request = ChatRouteRequest(
message="Hello",
has_files=False,
has_create_session=False,
has_edit_session=False,
last_route="none",
history=[],
)
expected_response = ChatRouteResponse(
intent="edit",
edit_intent=EditIntentHint(mode="info", requires_file_context=False),
reason="Conversational greeting",
)
with monkeypatch.context() as m:
m.setattr("chat_router.run_ai", lambda *args, **kwargs: expected_response)
response = classify_chat_route(request)
assert response.intent == "edit"
assert response.edit_intent is not None
assert response.edit_intent.mode == "info"
def test_capability_question_without_files(monkeypatch: MonkeyPatch):
"""'What can you do?' should route to edit/info"""
request = ChatRouteRequest(
message="What can you do?",
has_files=False,
has_create_session=False,
has_edit_session=False,
last_route="none",
history=[],
)
expected_response = ChatRouteResponse(
intent="edit",
edit_intent=EditIntentHint(mode="info", requires_file_context=False),
reason="User asking about capabilities",
)
with monkeypatch.context() as m:
m.setattr("chat_router.run_ai", lambda *args, **kwargs: expected_response)
response = classify_chat_route(request)
assert response.intent == "edit"
assert response.edit_intent is not None
assert response.edit_intent.mode == "info"
def test_help_request(monkeypatch: MonkeyPatch):
"""Help requests should route to edit/info"""
request = ChatRouteRequest(
message="help",
has_files=False,
has_create_session=False,
has_edit_session=False,
last_route="none",
history=[],
)
expected_response = ChatRouteResponse(
intent="edit",
edit_intent=EditIntentHint(mode="info", requires_file_context=False),
reason="Help request",
)
with monkeypatch.context() as m:
m.setattr("chat_router.run_ai", lambda *args, **kwargs: expected_response)
response = classify_chat_route(request)
assert response.intent == "edit"
assert response.edit_intent is not None
assert response.edit_intent.mode == "info"
def test_actual_document_creation(monkeypatch: MonkeyPatch):
"""Explicit creation requests should still route to create"""
request = ChatRouteRequest(
message="Create a business proposal document",
has_files=False,
has_create_session=False,
has_edit_session=False,
last_route="none",
history=[],
)
expected_response = ChatRouteResponse(
intent="create",
create_intent=CreateIntentHint(action="start"),
reason="User wants to create new document",
)
with monkeypatch.context() as m:
m.setattr("chat_router.run_ai", lambda *args, **kwargs: expected_response)
response = classify_chat_route(request)
assert response.intent == "create"
assert response.create_intent is not None
assert response.create_intent.action == "start"
def test_edit_command_with_files(monkeypatch: MonkeyPatch):
"""Edit commands should still route to edit/command"""
request = ChatRouteRequest(
message="Compress this PDF",
has_files=True,
has_create_session=False,
has_edit_session=False,
last_route="none",
history=[],
)
expected_response = ChatRouteResponse(
intent="edit",
edit_intent=EditIntentHint(mode="command", requires_file_context=False),
reason="User wants to compress PDF",
)
with monkeypatch.context() as m:
m.setattr("chat_router.run_ai", lambda *args, **kwargs: expected_response)
response = classify_chat_route(request)
assert response.intent == "edit"
assert response.edit_intent is not None
assert response.edit_intent.mode == "command"

View File

@@ -0,0 +1,52 @@
from __future__ import annotations
from pytest import MonkeyPatch
from editing.decisions import answer_conversational_info
from file_processing_agent import ToolCatalogService
def test_answer_conversational_info_greeting(monkeypatch: MonkeyPatch):
"""Test handling of greeting without files"""
class MockResponse:
message = "Hello! I can help you with PDF operations like compress, merge, split, and more."
def mock_run_ai(*args, **kwargs):
return MockResponse()
with monkeypatch.context() as m:
m.setattr("editing.decisions.run_ai", mock_run_ai)
tool_catalog = ToolCatalogService()
result = answer_conversational_info(
message="Hello",
history=[],
tool_catalog=tool_catalog,
)
assert isinstance(result, str)
assert len(result) > 0
def test_answer_conversational_info_capabilities(monkeypatch: MonkeyPatch):
"""Test handling of capability questions"""
class MockResponse:
message = "I can help with compress, merge, split, rotate, watermark, OCR, and many other PDF operations."
def mock_run_ai(*args, **kwargs):
return MockResponse()
with monkeypatch.context() as m:
m.setattr("editing.decisions.run_ai", mock_run_ai)
tool_catalog = ToolCatalogService()
result = answer_conversational_info(
message="What can you do?",
history=[],
tool_catalog=tool_catalog,
)
assert isinstance(result, str)
assert len(result) > 0

31
engine/tests/test_env.py Normal file
View File

@@ -0,0 +1,31 @@
from __future__ import annotations
import re
from pathlib import Path
from dotenv import dotenv_values
ENGINE_ROOT = Path(__file__).parent.parent
SRC_DIR = ENGINE_ROOT / "src"
EXAMPLE_FILE = ENGINE_ROOT / "config" / ".env.example"
def _parse_example_keys() -> set[str]:
return set(dotenv_values(EXAMPLE_FILE).keys())
def _find_stirling_env_vars() -> set[str]:
env_vars: set[str] = set()
for path in SRC_DIR.rglob("*.py"):
for match in re.finditer(r"\b(STIRLING_\w+)\b", path.read_text()):
env_vars.add(match.group(1))
return env_vars
def test_every_stirling_env_var_is_in_example_file():
example_keys = _parse_example_keys()
source_vars = _find_stirling_env_vars()
missing = sorted(source_vars - example_keys)
assert not missing, "env vars used in src/ but missing from config/.env.example:\n" + "\n".join(
f" {v}" for v in missing
)

View File

@@ -0,0 +1,45 @@
from __future__ import annotations
from pytest import MonkeyPatch
from models import (
AvailableTool,
SmartFolderAutomation,
SmartFolderConfig,
SmartFolderCreateRequest,
SmartFolderCreateResponse,
SmartFolderOperation,
)
from smart_folder_creator import create_smart_folder_config
def _build_sample_response() -> SmartFolderCreateResponse:
return SmartFolderCreateResponse(
assistant_message="I will build that folder for you.",
smart_folder_config=SmartFolderConfig(
name="Email Prep",
description="Compress and split for email",
automation=SmartFolderAutomation(
name="Email Cleanup",
description="Email prep steps",
operations=[SmartFolderOperation(operation="compress-pdf", parameters='{"compressionLevel": 3}')],
),
icon="mail",
accent_color="#0ea5e9",
),
)
def test_create_smart_folder_config_calls_ai_and_returns_response(monkeypatch: MonkeyPatch):
request = SmartFolderCreateRequest(
message="Create a folder that zips attachments",
history=[],
available_tools=[AvailableTool(id="compress-pdf", name="Compress PDFs")],
)
response_value = _build_sample_response()
with monkeypatch.context() as m:
m.setattr("smart_folder_creator.run_ai", lambda *args, **kwargs: response_value)
result = create_smart_folder_config(request)
assert result == response_value