rossum-agent 1.0.0rc0__py3-none-any.whl
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.
- rossum_agent/__init__.py +9 -0
- rossum_agent/agent/__init__.py +32 -0
- rossum_agent/agent/core.py +932 -0
- rossum_agent/agent/memory.py +176 -0
- rossum_agent/agent/models.py +160 -0
- rossum_agent/agent/request_classifier.py +152 -0
- rossum_agent/agent/skills.py +132 -0
- rossum_agent/agent/types.py +5 -0
- rossum_agent/agent_logging.py +56 -0
- rossum_agent/api/__init__.py +1 -0
- rossum_agent/api/cli.py +51 -0
- rossum_agent/api/dependencies.py +190 -0
- rossum_agent/api/main.py +180 -0
- rossum_agent/api/models/__init__.py +1 -0
- rossum_agent/api/models/schemas.py +301 -0
- rossum_agent/api/routes/__init__.py +1 -0
- rossum_agent/api/routes/chats.py +95 -0
- rossum_agent/api/routes/files.py +113 -0
- rossum_agent/api/routes/health.py +44 -0
- rossum_agent/api/routes/messages.py +218 -0
- rossum_agent/api/services/__init__.py +1 -0
- rossum_agent/api/services/agent_service.py +451 -0
- rossum_agent/api/services/chat_service.py +197 -0
- rossum_agent/api/services/file_service.py +65 -0
- rossum_agent/assets/Primary_light_logo.png +0 -0
- rossum_agent/bedrock_client.py +64 -0
- rossum_agent/prompts/__init__.py +27 -0
- rossum_agent/prompts/base_prompt.py +80 -0
- rossum_agent/prompts/system_prompt.py +24 -0
- rossum_agent/py.typed +0 -0
- rossum_agent/redis_storage.py +482 -0
- rossum_agent/rossum_mcp_integration.py +123 -0
- rossum_agent/skills/hook-debugging.md +31 -0
- rossum_agent/skills/organization-setup.md +60 -0
- rossum_agent/skills/rossum-deployment.md +102 -0
- rossum_agent/skills/schema-patching.md +61 -0
- rossum_agent/skills/schema-pruning.md +23 -0
- rossum_agent/skills/ui-settings.md +45 -0
- rossum_agent/streamlit_app/__init__.py +1 -0
- rossum_agent/streamlit_app/app.py +646 -0
- rossum_agent/streamlit_app/beep_sound.py +36 -0
- rossum_agent/streamlit_app/cli.py +17 -0
- rossum_agent/streamlit_app/render_modules.py +123 -0
- rossum_agent/streamlit_app/response_formatting.py +305 -0
- rossum_agent/tools/__init__.py +214 -0
- rossum_agent/tools/core.py +173 -0
- rossum_agent/tools/deploy.py +404 -0
- rossum_agent/tools/dynamic_tools.py +365 -0
- rossum_agent/tools/file_tools.py +62 -0
- rossum_agent/tools/formula.py +187 -0
- rossum_agent/tools/skills.py +31 -0
- rossum_agent/tools/spawn_mcp.py +227 -0
- rossum_agent/tools/subagents/__init__.py +31 -0
- rossum_agent/tools/subagents/base.py +303 -0
- rossum_agent/tools/subagents/hook_debug.py +591 -0
- rossum_agent/tools/subagents/knowledge_base.py +305 -0
- rossum_agent/tools/subagents/mcp_helpers.py +47 -0
- rossum_agent/tools/subagents/schema_patching.py +471 -0
- rossum_agent/url_context.py +167 -0
- rossum_agent/user_detection.py +100 -0
- rossum_agent/utils.py +128 -0
- rossum_agent-1.0.0rc0.dist-info/METADATA +311 -0
- rossum_agent-1.0.0rc0.dist-info/RECORD +67 -0
- rossum_agent-1.0.0rc0.dist-info/WHEEL +5 -0
- rossum_agent-1.0.0rc0.dist-info/entry_points.txt +3 -0
- rossum_agent-1.0.0rc0.dist-info/licenses/LICENSE +21 -0
- rossum_agent-1.0.0rc0.dist-info/top_level.txt +1 -0
rossum_agent/api/cli.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""CLI for testing API SSE events."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main() -> None:
|
|
13
|
+
"""Send a prompt to the API and print SSE events."""
|
|
14
|
+
parser = argparse.ArgumentParser(description="Test Rossum Agent API SSE events")
|
|
15
|
+
parser.add_argument("prompt", nargs="?", help="Prompt to send")
|
|
16
|
+
parser.add_argument("--api-url", default="http://127.0.0.1:8000", help="API base URL")
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
token = os.environ.get("ROSSUM_API_TOKEN")
|
|
20
|
+
rossum_url = os.environ.get("ROSSUM_API_BASE_URL")
|
|
21
|
+
|
|
22
|
+
if not token:
|
|
23
|
+
print("Error: ROSSUM_API_TOKEN is required.", file=sys.stderr)
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
if not rossum_url:
|
|
26
|
+
print("Error: ROSSUM_API_BASE_URL is required.", file=sys.stderr)
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
assert token and rossum_url
|
|
29
|
+
|
|
30
|
+
prompt = args.prompt or input("Prompt: ")
|
|
31
|
+
headers: dict[str, str] = {"X-Rossum-Token": token, "X-Rossum-Api-Url": rossum_url}
|
|
32
|
+
|
|
33
|
+
with httpx.Client(timeout=300) as client:
|
|
34
|
+
resp = client.post(f"{args.api_url}/api/v1/chats", headers=headers)
|
|
35
|
+
resp.raise_for_status()
|
|
36
|
+
data = resp.json()
|
|
37
|
+
chat_id = data.get("id") or data.get("chat_id")
|
|
38
|
+
print(f"Created chat: {chat_id} (response: {data})\n")
|
|
39
|
+
|
|
40
|
+
print(f"{'=' * 60}\nSSE EVENTS:\n{'=' * 60}")
|
|
41
|
+
with client.stream(
|
|
42
|
+
"POST", f"{args.api_url}/api/v1/chats/{chat_id}/messages", headers=headers, json={"content": prompt}
|
|
43
|
+
) as response:
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
for line in response.iter_lines():
|
|
46
|
+
if line:
|
|
47
|
+
print(line)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
main()
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""FastAPI dependencies for the API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Annotated # noqa: TC003 - Required at runtime for FastAPI dependency injection
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
from fastapi import Header, HTTPException, status
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Base allowed hosts pattern
|
|
19
|
+
_BASE_ALLOWED_HOSTS = (
|
|
20
|
+
r"elis\.rossum\.ai|api\.elis\.rossum\.ai|(.*\.)?api\.rossum\.ai|.*\.rossum\.app|(elis|api\.elis)\.develop\.r8\.lol"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Additional hosts from environment variable (comma-separated regex patterns)
|
|
24
|
+
# Example: ADDITIONAL_ALLOWED_ROSSUM_HOSTS=".*\.review\.r8\.lol,.*\.staging\.example\.com"
|
|
25
|
+
_ADDITIONAL_HOSTS = os.environ.get("ADDITIONAL_ALLOWED_ROSSUM_HOSTS", "")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _build_allowed_hosts_pattern() -> re.Pattern[str]:
|
|
29
|
+
"""Build the allowed hosts regex pattern including any additional hosts."""
|
|
30
|
+
patterns = [_BASE_ALLOWED_HOSTS]
|
|
31
|
+
if _ADDITIONAL_HOSTS:
|
|
32
|
+
additional = [p.strip() for p in _ADDITIONAL_HOSTS.split(",") if p.strip()]
|
|
33
|
+
patterns.extend(additional)
|
|
34
|
+
return re.compile(f"^({'|'.join(patterns)})$")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
ALLOWED_ROSSUM_HOST_PATTERN = _build_allowed_hosts_pattern()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def validate_rossum_api_url(url: str) -> str:
|
|
41
|
+
"""Validate that the Rossum API URL is a trusted Rossum domain.
|
|
42
|
+
|
|
43
|
+
This prevents SSRF attacks by ensuring we only make requests to known Rossum endpoints.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
url: The API URL to validate.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The validated and normalized API base URL.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
HTTPException: If the URL is not a valid Rossum API endpoint.
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
parsed = urlparse(url)
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid X-Rossum-Api-Url format") from e
|
|
58
|
+
|
|
59
|
+
if parsed.scheme != "https":
|
|
60
|
+
raise HTTPException(
|
|
61
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
62
|
+
detail="X-Rossum-Api-Url must use HTTPS",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not parsed.hostname:
|
|
66
|
+
raise HTTPException(
|
|
67
|
+
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid X-Rossum-Api-Url: missing hostname"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not ALLOWED_ROSSUM_HOST_PATTERN.match(parsed.hostname):
|
|
71
|
+
logger.warning(f"Rejected non-Rossum API URL: {parsed.hostname}")
|
|
72
|
+
raise HTTPException(
|
|
73
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
74
|
+
detail="X-Rossum-Api-Url must be a valid Rossum API endpoint",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
api_base = f"{parsed.scheme}://{parsed.hostname}"
|
|
78
|
+
if parsed.port and parsed.port != 443:
|
|
79
|
+
api_base = f"{api_base}:{parsed.port}"
|
|
80
|
+
|
|
81
|
+
# Preserve /api prefix if present (some Rossum environments use /api/v1 path)
|
|
82
|
+
if parsed.path:
|
|
83
|
+
path = parsed.path.rstrip("/")
|
|
84
|
+
# Strip /v1 suffix to avoid duplication when we append /v1/auth/user
|
|
85
|
+
if path.endswith("/v1"):
|
|
86
|
+
path = path[:-3]
|
|
87
|
+
if path:
|
|
88
|
+
api_base = f"{api_base}{path}"
|
|
89
|
+
|
|
90
|
+
return api_base
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass
|
|
94
|
+
class RossumCredentials:
|
|
95
|
+
"""Rossum API credentials extracted from request headers."""
|
|
96
|
+
|
|
97
|
+
token: str
|
|
98
|
+
api_url: str
|
|
99
|
+
user_id: str | None = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def get_rossum_credentials(
|
|
103
|
+
x_rossum_token: Annotated[str, Header(alias="X-Rossum-Token")],
|
|
104
|
+
x_rossum_api_url: Annotated[str, Header(alias="X-Rossum-Api-Url")],
|
|
105
|
+
) -> RossumCredentials:
|
|
106
|
+
"""Extract and validate Rossum credentials from request headers.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
x_rossum_token: Rossum API token from X-Rossum-Token header.
|
|
110
|
+
x_rossum_api_url: Rossum API URL from X-Rossum-Api-Url header.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
RossumCredentials with token and API URL.
|
|
114
|
+
|
|
115
|
+
Raises:
|
|
116
|
+
HTTPException: If credentials are missing or invalid.
|
|
117
|
+
"""
|
|
118
|
+
if not x_rossum_token:
|
|
119
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing X-Rossum-Token header")
|
|
120
|
+
if not x_rossum_api_url:
|
|
121
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing X-Rossum-Api-Url header")
|
|
122
|
+
|
|
123
|
+
return RossumCredentials(token=x_rossum_token, api_url=x_rossum_api_url)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
async def get_validated_credentials(
|
|
127
|
+
x_rossum_token: Annotated[str, Header(alias="X-Rossum-Token")],
|
|
128
|
+
x_rossum_api_url: Annotated[str, Header(alias="X-Rossum-Api-Url")],
|
|
129
|
+
) -> RossumCredentials:
|
|
130
|
+
"""Extract credentials and validate against Rossum API.
|
|
131
|
+
|
|
132
|
+
Validates the token by calling the Rossum API /v1/auth/user endpoint.
|
|
133
|
+
Extracts user ID from the response for user isolation.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
x_rossum_token: Rossum API token from X-Rossum-Token header.
|
|
137
|
+
x_rossum_api_url: Rossum API URL from X-Rossum-Api-Url header.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
RossumCredentials with token, API URL, and user_id.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
HTTPException: If credentials are missing or invalid.
|
|
144
|
+
"""
|
|
145
|
+
credentials = await get_rossum_credentials(x_rossum_token, x_rossum_api_url)
|
|
146
|
+
|
|
147
|
+
# Validate and normalize API URL to prevent SSRF
|
|
148
|
+
api_base = validate_rossum_api_url(credentials.api_url)
|
|
149
|
+
# Strip trailing /v1 to avoid duplication (URL might be .../api or .../api/v1)
|
|
150
|
+
api_base_normalized = api_base.rstrip("/")
|
|
151
|
+
if api_base_normalized.endswith("/v1"):
|
|
152
|
+
api_base_normalized = api_base_normalized[:-3]
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
async with httpx.AsyncClient() as client:
|
|
156
|
+
response = await client.get(
|
|
157
|
+
f"{api_base_normalized}/v1/auth/user",
|
|
158
|
+
headers={"Authorization": f"Bearer {credentials.token}"},
|
|
159
|
+
timeout=10.0,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if response.status_code == 401:
|
|
163
|
+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Rossum API token")
|
|
164
|
+
|
|
165
|
+
if response.status_code != 200:
|
|
166
|
+
logger.warning(f"Rossum API returned {response.status_code}")
|
|
167
|
+
raise HTTPException(
|
|
168
|
+
status_code=status.HTTP_502_BAD_GATEWAY, detail="Failed to validate token with Rossum API"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
user_data = response.json()
|
|
173
|
+
except json.JSONDecodeError as e:
|
|
174
|
+
logger.error(f"Rossum API returned invalid JSON: {e}. Response text: {response.text!r}")
|
|
175
|
+
raise HTTPException(
|
|
176
|
+
status_code=status.HTTP_502_BAD_GATEWAY, detail="Rossum API returned invalid response"
|
|
177
|
+
) from e
|
|
178
|
+
|
|
179
|
+
user_id = str(user_data.get("id", ""))
|
|
180
|
+
|
|
181
|
+
if not user_id:
|
|
182
|
+
raise HTTPException(
|
|
183
|
+
status_code=status.HTTP_502_BAD_GATEWAY, detail="Rossum API did not return user ID"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
return RossumCredentials(token=credentials.token, api_url=credentials.api_url, user_id=user_id)
|
|
187
|
+
|
|
188
|
+
except httpx.RequestError as e:
|
|
189
|
+
logger.error(f"Failed to connect to Rossum API: {e}")
|
|
190
|
+
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY, detail="Failed to connect to Rossum API") from e
|
rossum_agent/api/main.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""FastAPI application entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from fastapi import FastAPI, Request, status
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from collections.abc import AsyncGenerator
|
|
16
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
17
|
+
from fastapi.responses import JSONResponse
|
|
18
|
+
from slowapi import Limiter
|
|
19
|
+
from slowapi.errors import RateLimitExceeded
|
|
20
|
+
from slowapi.util import get_remote_address
|
|
21
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
22
|
+
|
|
23
|
+
from rossum_agent.api.routes import chats, files, health, messages
|
|
24
|
+
from rossum_agent.api.services.agent_service import AgentService
|
|
25
|
+
from rossum_agent.api.services.chat_service import ChatService
|
|
26
|
+
from rossum_agent.api.services.file_service import FileService
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
MAX_REQUEST_SIZE = 10 * 1024 * 1024 # 10 MB (supports image uploads)
|
|
31
|
+
|
|
32
|
+
limiter = Limiter(key_func=get_remote_address)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RequestSizeLimitMiddleware(BaseHTTPMiddleware):
|
|
36
|
+
"""Middleware to limit request body size."""
|
|
37
|
+
|
|
38
|
+
async def dispatch(self, request: Request, call_next):
|
|
39
|
+
content_length = request.headers.get("content-length")
|
|
40
|
+
if content_length and int(content_length) > MAX_REQUEST_SIZE:
|
|
41
|
+
return JSONResponse(
|
|
42
|
+
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
|
43
|
+
content={"detail": f"Request body too large. Maximum size is {MAX_REQUEST_SIZE // 1024} KB."},
|
|
44
|
+
)
|
|
45
|
+
return await call_next(request)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def rate_limit_exceeded_handler(request: Request, exc: RateLimitExceeded) -> JSONResponse:
|
|
49
|
+
"""Handle rate limit exceeded errors."""
|
|
50
|
+
return JSONResponse(
|
|
51
|
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
52
|
+
content={"detail": f"Rate limit exceeded: {exc.detail}"},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_chat_service: ChatService | None = None
|
|
57
|
+
_agent_service: AgentService | None = None
|
|
58
|
+
_file_service: FileService | None = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_chat_service() -> ChatService:
|
|
62
|
+
"""Get the shared ChatService instance."""
|
|
63
|
+
global _chat_service
|
|
64
|
+
if _chat_service is None:
|
|
65
|
+
_chat_service = ChatService()
|
|
66
|
+
return _chat_service
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_agent_service() -> AgentService:
|
|
70
|
+
"""Get the shared AgentService instance."""
|
|
71
|
+
global _agent_service
|
|
72
|
+
if _agent_service is None:
|
|
73
|
+
_agent_service = AgentService()
|
|
74
|
+
return _agent_service
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_file_service() -> FileService:
|
|
78
|
+
"""Get the shared FileService instance."""
|
|
79
|
+
global _file_service
|
|
80
|
+
if _file_service is None:
|
|
81
|
+
_file_service = FileService(get_chat_service().storage)
|
|
82
|
+
return _file_service
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@asynccontextmanager
|
|
86
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
|
87
|
+
"""Lifespan context manager for startup and shutdown events."""
|
|
88
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
|
89
|
+
logger.info("Rossum Agent API starting up...")
|
|
90
|
+
|
|
91
|
+
chat_service = get_chat_service()
|
|
92
|
+
if chat_service.is_connected():
|
|
93
|
+
logger.info("Redis connection established")
|
|
94
|
+
else:
|
|
95
|
+
logger.warning("Redis connection failed - some features may not work")
|
|
96
|
+
|
|
97
|
+
yield
|
|
98
|
+
|
|
99
|
+
logger.info("Rossum Agent API shutting down...")
|
|
100
|
+
if _chat_service is not None:
|
|
101
|
+
_chat_service.storage.close()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
app = FastAPI(
|
|
105
|
+
title="Rossum Agent API",
|
|
106
|
+
description="REST API for Rossum Agent - AI-powered document processing assistant",
|
|
107
|
+
version="0.2.0",
|
|
108
|
+
docs_url="/api/docs",
|
|
109
|
+
redoc_url="/api/redoc",
|
|
110
|
+
openapi_url="/api/openapi.json",
|
|
111
|
+
lifespan=lifespan,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
app.state.limiter = limiter
|
|
115
|
+
app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
|
|
116
|
+
|
|
117
|
+
app.add_middleware(RequestSizeLimitMiddleware)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _build_cors_origin_regex() -> str:
|
|
121
|
+
"""Build CORS origin regex including any additional allowed hosts."""
|
|
122
|
+
patterns = [r".*\.rossum\.app"]
|
|
123
|
+
additional_hosts = os.environ.get("ADDITIONAL_ALLOWED_ROSSUM_HOSTS", "")
|
|
124
|
+
if additional_hosts:
|
|
125
|
+
patterns.extend(p.strip() for p in additional_hosts.split(",") if p.strip())
|
|
126
|
+
return rf"https://({'|'.join(patterns)})"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
app.add_middleware(
|
|
130
|
+
CORSMiddleware,
|
|
131
|
+
allow_origins=[
|
|
132
|
+
"https://elis.rossum.ai",
|
|
133
|
+
"https://elis.develop.r8.lol",
|
|
134
|
+
],
|
|
135
|
+
allow_origin_regex=_build_cors_origin_regex(),
|
|
136
|
+
allow_credentials=True,
|
|
137
|
+
allow_methods=["*"],
|
|
138
|
+
allow_headers=["*"],
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
health.set_chat_service_getter(get_chat_service)
|
|
142
|
+
chats.set_chat_service_getter(get_chat_service)
|
|
143
|
+
messages.set_chat_service_getter(get_chat_service)
|
|
144
|
+
messages.set_agent_service_getter(get_agent_service)
|
|
145
|
+
files.set_chat_service_getter(get_chat_service)
|
|
146
|
+
files.set_file_service_getter(get_file_service)
|
|
147
|
+
|
|
148
|
+
app.include_router(health.router, prefix="/api/v1")
|
|
149
|
+
app.include_router(chats.router, prefix="/api/v1")
|
|
150
|
+
app.include_router(messages.router, prefix="/api/v1")
|
|
151
|
+
app.include_router(files.router, prefix="/api/v1")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main() -> None:
|
|
155
|
+
"""CLI entry point for the API server."""
|
|
156
|
+
parser = argparse.ArgumentParser(description="Run the Rossum Agent API server")
|
|
157
|
+
parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)")
|
|
158
|
+
parser.add_argument("--port", type=int, default=8000, help="Port to listen on (default: 8000)")
|
|
159
|
+
parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development")
|
|
160
|
+
parser.add_argument("--workers", type=int, default=1, help="Number of worker processes (default: 1)")
|
|
161
|
+
|
|
162
|
+
args = parser.parse_args()
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
import uvicorn # noqa: PLC0415
|
|
166
|
+
except ImportError:
|
|
167
|
+
print("Error: uvicorn is required. Install with: uv pip install 'rossum-agent[api]'")
|
|
168
|
+
sys.exit(1)
|
|
169
|
+
|
|
170
|
+
uvicorn.run(
|
|
171
|
+
"rossum_agent.api.main:app",
|
|
172
|
+
host=args.host,
|
|
173
|
+
port=args.port,
|
|
174
|
+
reload=args.reload,
|
|
175
|
+
workers=args.workers if not args.reload else 1,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Pydantic models for API requests and responses."""
|