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
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Core module with shared types, callbacks, and MCP state management.
|
|
2
|
+
|
|
3
|
+
This module provides the foundational types and state management used by
|
|
4
|
+
all internal tools. Uses contextvars for thread-safe state management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
from contextvars import ContextVar
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
import asyncio
|
|
18
|
+
|
|
19
|
+
from rossum_agent.rossum_mcp_integration import MCPConnection
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class SubAgentProgress:
|
|
24
|
+
"""Progress information from a sub-agent (e.g., debug_hook's Opus sub-agent)."""
|
|
25
|
+
|
|
26
|
+
tool_name: str
|
|
27
|
+
iteration: int
|
|
28
|
+
max_iterations: int
|
|
29
|
+
current_tool: str | None = None
|
|
30
|
+
tool_calls: list[str] = field(default_factory=list)
|
|
31
|
+
status: str = "running"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class SubAgentTokenUsage:
|
|
36
|
+
"""Token usage from a sub-agent call."""
|
|
37
|
+
|
|
38
|
+
tool_name: str
|
|
39
|
+
input_tokens: int
|
|
40
|
+
output_tokens: int
|
|
41
|
+
iteration: int | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class SubAgentText:
|
|
46
|
+
"""Text output from a sub-agent (e.g., debug_hook's Opus sub-agent)."""
|
|
47
|
+
|
|
48
|
+
tool_name: str
|
|
49
|
+
text: str
|
|
50
|
+
is_final: bool = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
SubAgentProgressCallback = Callable[[SubAgentProgress], None]
|
|
54
|
+
SubAgentTextCallback = Callable[[SubAgentText], None]
|
|
55
|
+
SubAgentTokenCallback = Callable[[SubAgentTokenUsage], None]
|
|
56
|
+
|
|
57
|
+
# Context variables for thread-safe state management
|
|
58
|
+
_progress_callback: ContextVar[SubAgentProgressCallback | None] = ContextVar("progress_callback", default=None)
|
|
59
|
+
_text_callback: ContextVar[SubAgentTextCallback | None] = ContextVar("text_callback", default=None)
|
|
60
|
+
_token_callback: ContextVar[SubAgentTokenCallback | None] = ContextVar("token_callback", default=None)
|
|
61
|
+
_mcp_connection: ContextVar[MCPConnection | None] = ContextVar("mcp_connection", default=None)
|
|
62
|
+
_mcp_event_loop: ContextVar[asyncio.AbstractEventLoop | None] = ContextVar("mcp_event_loop", default=None)
|
|
63
|
+
_output_dir: ContextVar[Path | None] = ContextVar("output_dir", default=None)
|
|
64
|
+
_rossum_credentials: ContextVar[tuple[str, str] | None] = ContextVar("rossum_credentials", default=None)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def set_progress_callback(callback: SubAgentProgressCallback | None) -> None:
|
|
68
|
+
"""Set the progress callback for sub-agent progress reporting."""
|
|
69
|
+
_progress_callback.set(callback)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def set_text_callback(callback: SubAgentTextCallback | None) -> None:
|
|
73
|
+
"""Set the text callback for sub-agent text reporting."""
|
|
74
|
+
_text_callback.set(callback)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def set_token_callback(callback: SubAgentTokenCallback | None) -> None:
|
|
78
|
+
"""Set the token callback for sub-agent token usage reporting."""
|
|
79
|
+
_token_callback.set(callback)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def report_progress(progress: SubAgentProgress) -> None:
|
|
83
|
+
"""Report progress via the callback if set."""
|
|
84
|
+
if (callback := _progress_callback.get()) is not None:
|
|
85
|
+
callback(progress)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def report_text(text: SubAgentText) -> None:
|
|
89
|
+
"""Report text via the callback if set."""
|
|
90
|
+
if (callback := _text_callback.get()) is not None:
|
|
91
|
+
callback(text)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def report_token_usage(usage: SubAgentTokenUsage) -> None:
|
|
95
|
+
"""Report token usage via the callback if set."""
|
|
96
|
+
if (callback := _token_callback.get()) is not None:
|
|
97
|
+
callback(usage)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def set_output_dir(output_dir: Path | None) -> None:
|
|
101
|
+
"""Set the output directory for internal tools."""
|
|
102
|
+
_output_dir.set(output_dir)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_output_dir() -> Path:
|
|
106
|
+
"""Get the output directory for internal tools."""
|
|
107
|
+
if (output_dir := _output_dir.get()) is not None:
|
|
108
|
+
return output_dir
|
|
109
|
+
fallback = Path("./outputs")
|
|
110
|
+
fallback.mkdir(exist_ok=True)
|
|
111
|
+
return fallback
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def set_mcp_connection(connection: MCPConnection | None, loop: asyncio.AbstractEventLoop | None) -> None:
|
|
115
|
+
"""Set the MCP connection for use by internal tools (pass None to clear)."""
|
|
116
|
+
_mcp_connection.set(connection)
|
|
117
|
+
_mcp_event_loop.set(loop)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def get_mcp_connection() -> MCPConnection | None:
|
|
121
|
+
"""Get the current MCP connection."""
|
|
122
|
+
return _mcp_connection.get()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_mcp_event_loop() -> asyncio.AbstractEventLoop | None:
|
|
126
|
+
"""Get the current MCP event loop."""
|
|
127
|
+
return _mcp_event_loop.get()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def set_rossum_credentials(api_base_url: str | None, token: str | None) -> None:
|
|
131
|
+
"""Set Rossum API credentials for internal tools.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
api_base_url: Rossum API base URL.
|
|
135
|
+
token: Rossum API token.
|
|
136
|
+
"""
|
|
137
|
+
if api_base_url and token:
|
|
138
|
+
_rossum_credentials.set((api_base_url, token))
|
|
139
|
+
else:
|
|
140
|
+
_rossum_credentials.set(None)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_rossum_credentials() -> tuple[str, str] | None:
|
|
144
|
+
"""Get Rossum API credentials from context or environment.
|
|
145
|
+
|
|
146
|
+
Checks context first (set by API service), then falls back to environment variables.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Tuple of (api_base_url, token) or None if neither context nor env vars are set.
|
|
150
|
+
"""
|
|
151
|
+
if (creds := _rossum_credentials.get()) is not None:
|
|
152
|
+
return creds
|
|
153
|
+
|
|
154
|
+
api_base = os.getenv("ROSSUM_API_BASE_URL")
|
|
155
|
+
token = os.getenv("ROSSUM_API_TOKEN")
|
|
156
|
+
if api_base and token:
|
|
157
|
+
return api_base, token
|
|
158
|
+
|
|
159
|
+
return None
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def require_rossum_credentials() -> tuple[str, str]:
|
|
163
|
+
"""Get Rossum API credentials, raising if unavailable.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Tuple of (api_base_url, token).
|
|
167
|
+
|
|
168
|
+
Raises:
|
|
169
|
+
ValueError: If credentials are not available.
|
|
170
|
+
"""
|
|
171
|
+
if (creds := get_rossum_credentials()) is not None:
|
|
172
|
+
return creds
|
|
173
|
+
raise ValueError("Rossum API credentials not available (neither in context nor environment variables)")
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""Deployment tools for the Rossum Agent.
|
|
2
|
+
|
|
3
|
+
This module provides tools for managing Rossum configuration deployments,
|
|
4
|
+
including pull, diff, push, and cross-org copy operations using rossum-deploy.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from anthropic import beta_tool
|
|
15
|
+
from rossum_deploy.models import IdMapping
|
|
16
|
+
from rossum_deploy.workspace import Workspace
|
|
17
|
+
|
|
18
|
+
from rossum_agent.tools.core import get_output_dir, require_rossum_credentials
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from anthropic._tools import BetaTool # ty: ignore[unresolved-import] - private API
|
|
22
|
+
from anthropic.types import ToolParam
|
|
23
|
+
from rossum_deploy.workspace import Workspace as WorkspaceType
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_workspace(
|
|
29
|
+
path: str | None = None, api_base_url: str | None = None, token: str | None = None
|
|
30
|
+
) -> WorkspaceType:
|
|
31
|
+
"""Create a Workspace instance for deployment operations."""
|
|
32
|
+
default_api_base, default_token = require_rossum_credentials()
|
|
33
|
+
api_base = api_base_url or default_api_base
|
|
34
|
+
api_token = token or default_token
|
|
35
|
+
|
|
36
|
+
workspace_path = Path(path) if path else get_output_dir() / "rossum-config"
|
|
37
|
+
workspace_path.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
|
|
39
|
+
return Workspace(workspace_path, api_base=api_base, token=api_token)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@beta_tool
|
|
43
|
+
def deploy_pull(
|
|
44
|
+
org_id: int, workspace_path: str | None = None, api_base_url: str | None = None, token: str | None = None
|
|
45
|
+
) -> str:
|
|
46
|
+
"""Pull Rossum configuration objects from an organization to local files.
|
|
47
|
+
|
|
48
|
+
Downloads workspaces, queues, schemas, hooks, inboxes, and other objects
|
|
49
|
+
to local JSON files for version control and deployment workflows.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
org_id: The organization ID to pull from.
|
|
53
|
+
workspace_path: Optional path to the workspace directory.
|
|
54
|
+
Defaults to './rossum-config' in the session output directory.
|
|
55
|
+
api_base_url: Optional API base URL for the target environment.
|
|
56
|
+
Use this when pulling from sandbox/different environment.
|
|
57
|
+
token: Optional API token for the target environment.
|
|
58
|
+
Use this when pulling from sandbox/different environment.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
JSON with pull summary including counts of pulled objects.
|
|
62
|
+
"""
|
|
63
|
+
logger.info(f"deploy_pull called with {org_id=}, {workspace_path=}, {api_base_url=}")
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
ws = create_workspace(workspace_path, api_base_url=api_base_url, token=token)
|
|
67
|
+
result = ws.pull(org_id=org_id)
|
|
68
|
+
|
|
69
|
+
return json.dumps(
|
|
70
|
+
{
|
|
71
|
+
"status": "success",
|
|
72
|
+
"summary": result.summary(),
|
|
73
|
+
"pulled_count": len(result.pulled),
|
|
74
|
+
"skipped_count": len(result.skipped),
|
|
75
|
+
"workspace_path": str(ws.path),
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.exception("Error in deploy_pull")
|
|
80
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@beta_tool
|
|
84
|
+
def deploy_diff(workspace_path: str | None = None) -> str:
|
|
85
|
+
"""Compare local workspace files with remote Rossum configuration.
|
|
86
|
+
|
|
87
|
+
Shows which objects have been modified locally, remotely, or have conflicts.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
workspace_path: Optional path to the workspace directory.
|
|
91
|
+
Defaults to './rossum-config' in the session output directory.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
JSON with diff summary showing unchanged, modified, and conflicting objects.
|
|
95
|
+
"""
|
|
96
|
+
logger.info(f"deploy_diff called with {workspace_path=}")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
ws = create_workspace(workspace_path)
|
|
100
|
+
result = ws.diff()
|
|
101
|
+
|
|
102
|
+
return json.dumps(
|
|
103
|
+
{
|
|
104
|
+
"status": "success",
|
|
105
|
+
"summary": result.summary(),
|
|
106
|
+
"unchanged": result.total_unchanged,
|
|
107
|
+
"local_modified": result.total_local_modified,
|
|
108
|
+
"remote_modified": result.total_remote_modified,
|
|
109
|
+
"conflicts": result.total_conflicts,
|
|
110
|
+
"workspace_path": str(ws.path),
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.exception("Error in deploy_diff")
|
|
115
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@beta_tool
|
|
119
|
+
def deploy_push(dry_run: bool = False, force: bool = False, workspace_path: str | None = None) -> str:
|
|
120
|
+
"""Push local changes to Rossum.
|
|
121
|
+
|
|
122
|
+
Uploads modified local configuration to the remote Rossum organization.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
dry_run: If True, only show what would be pushed without making changes.
|
|
126
|
+
force: If True, push even if there are conflicts.
|
|
127
|
+
workspace_path: Optional path to the workspace directory.
|
|
128
|
+
Defaults to './rossum-config' in the session output directory.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
JSON with push summary including counts of pushed, skipped, and failed objects.
|
|
132
|
+
"""
|
|
133
|
+
logger.info(f"deploy_push called with {dry_run=}, {force=}, {workspace_path=}")
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
ws = create_workspace(workspace_path)
|
|
137
|
+
|
|
138
|
+
if dry_run:
|
|
139
|
+
result = ws.push(dry_run=True)
|
|
140
|
+
return json.dumps(
|
|
141
|
+
{
|
|
142
|
+
"status": "success",
|
|
143
|
+
"dry_run": True,
|
|
144
|
+
"summary": result.summary(),
|
|
145
|
+
"would_push_count": len(result.pushed),
|
|
146
|
+
"would_skip_count": len(result.skipped),
|
|
147
|
+
"workspace_path": str(ws.path),
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
result = ws.push(force=force)
|
|
152
|
+
return json.dumps(
|
|
153
|
+
{
|
|
154
|
+
"status": "success",
|
|
155
|
+
"dry_run": False,
|
|
156
|
+
"summary": result.summary(),
|
|
157
|
+
"pushed_count": len(result.pushed),
|
|
158
|
+
"skipped_count": len(result.skipped),
|
|
159
|
+
"failed_count": len(result.failed),
|
|
160
|
+
"workspace_path": str(ws.path),
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.exception("Error in deploy_push")
|
|
165
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@beta_tool
|
|
169
|
+
def deploy_copy_org(
|
|
170
|
+
source_org_id: int,
|
|
171
|
+
target_org_id: int,
|
|
172
|
+
target_api_base: str | None = None,
|
|
173
|
+
target_token: str | None = None,
|
|
174
|
+
workspace_path: str | None = None,
|
|
175
|
+
) -> str:
|
|
176
|
+
"""Copy all objects from source organization to target organization.
|
|
177
|
+
|
|
178
|
+
Creates copies of all workspaces, queues, schemas, hooks, and other objects
|
|
179
|
+
from source org to target org. Saves ID mappings for later deployment.
|
|
180
|
+
|
|
181
|
+
Use this to mirror production to sandbox before making changes.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
source_org_id: Source organization ID (e.g., production).
|
|
185
|
+
target_org_id: Target organization ID (e.g., sandbox).
|
|
186
|
+
target_api_base: Target API base URL if different from source.
|
|
187
|
+
target_token: Target API token if different from source.
|
|
188
|
+
workspace_path: Optional path to the workspace directory.
|
|
189
|
+
Defaults to './rossum-config' in the session output directory.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
JSON with copy summary including counts of created, skipped, and failed objects.
|
|
193
|
+
"""
|
|
194
|
+
logger.info(
|
|
195
|
+
f"deploy_copy_org called with {source_org_id=}, {target_org_id=}, {target_api_base=}, {workspace_path=}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
ws = create_workspace(workspace_path)
|
|
200
|
+
|
|
201
|
+
result = ws.copy_org(
|
|
202
|
+
source_org_id=source_org_id,
|
|
203
|
+
target_org_id=target_org_id,
|
|
204
|
+
target_api_base=target_api_base,
|
|
205
|
+
target_token=target_token,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return json.dumps(
|
|
209
|
+
{
|
|
210
|
+
"status": "success",
|
|
211
|
+
"summary": result.summary(),
|
|
212
|
+
"created_count": len(result.created),
|
|
213
|
+
"skipped_count": len(result.skipped),
|
|
214
|
+
"failed_count": len(result.failed),
|
|
215
|
+
"workspace_path": str(ws.path),
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.exception("Error in deploy_copy_org")
|
|
220
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@beta_tool
|
|
224
|
+
def deploy_copy_workspace(
|
|
225
|
+
source_workspace_id: int,
|
|
226
|
+
target_org_id: int,
|
|
227
|
+
target_api_base: str | None = None,
|
|
228
|
+
target_token: str | None = None,
|
|
229
|
+
workspace_path: str | None = None,
|
|
230
|
+
) -> str:
|
|
231
|
+
"""Copy a single workspace and all its objects to target organization.
|
|
232
|
+
|
|
233
|
+
Copies a workspace with all its queues, schemas, engines, hooks, connectors,
|
|
234
|
+
inboxes, email templates, and rules to the target organization.
|
|
235
|
+
|
|
236
|
+
Useful when you only need to replicate part of an org rather than the entire organization.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
source_workspace_id: Source workspace ID to copy.
|
|
240
|
+
target_org_id: Target organization ID to copy to.
|
|
241
|
+
target_api_base: Target API base URL if different from source.
|
|
242
|
+
target_token: Target API token if different from source.
|
|
243
|
+
workspace_path: Optional path to the workspace directory.
|
|
244
|
+
Defaults to './rossum-config' in the session output directory.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
JSON with copy summary including counts of created, skipped, and failed objects.
|
|
248
|
+
"""
|
|
249
|
+
logger.info(
|
|
250
|
+
f"deploy_copy_workspace called with {source_workspace_id=}, {target_org_id=}, {target_api_base=}, {workspace_path=}"
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
ws = create_workspace(workspace_path)
|
|
255
|
+
|
|
256
|
+
result = ws.copy_workspace(
|
|
257
|
+
source_workspace_id=source_workspace_id,
|
|
258
|
+
target_org_id=target_org_id,
|
|
259
|
+
target_api_base=target_api_base,
|
|
260
|
+
target_token=target_token,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return json.dumps(
|
|
264
|
+
{
|
|
265
|
+
"status": "success",
|
|
266
|
+
"summary": result.summary(),
|
|
267
|
+
"created_count": len(result.created),
|
|
268
|
+
"skipped_count": len(result.skipped),
|
|
269
|
+
"failed_count": len(result.failed),
|
|
270
|
+
"workspace_path": str(ws.path),
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
except Exception as e:
|
|
274
|
+
logger.exception("Error in deploy_copy_workspace")
|
|
275
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@beta_tool
|
|
279
|
+
def deploy_compare_workspaces(
|
|
280
|
+
source_workspace_path: str, target_workspace_path: str, id_mapping_path: str | None = None
|
|
281
|
+
) -> str:
|
|
282
|
+
"""Compare two local workspaces to see differences between source and target.
|
|
283
|
+
|
|
284
|
+
PREREQUISITE: Both directories must contain JSON files from deploy_pull.
|
|
285
|
+
This tool compares local files only - it does not fetch from remote APIs.
|
|
286
|
+
Call deploy_pull twice (before and after modifications) before using this tool.
|
|
287
|
+
|
|
288
|
+
Two use cases:
|
|
289
|
+
1. Compare prod vs sandbox: Pass id_mapping_path from copy_workspace to map IDs
|
|
290
|
+
2. Compare before vs after: Pass id_mapping_path=None to compare same workspace
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
source_workspace_path: Path to the source (original/production) workspace directory.
|
|
294
|
+
Must contain JSON files from deploy_pull.
|
|
295
|
+
target_workspace_path: Path to the target (modified/sandbox) workspace directory.
|
|
296
|
+
Must contain JSON files from deploy_pull.
|
|
297
|
+
id_mapping_path: Optional path to ID mapping JSON file from copy_workspace.
|
|
298
|
+
If None, objects are matched by their original IDs.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
JSON with comparison summary showing identical, different, source-only,
|
|
302
|
+
and target-only objects with field-level diffs.
|
|
303
|
+
"""
|
|
304
|
+
logger.info(
|
|
305
|
+
f"deploy_compare_workspaces called with {source_workspace_path=}, {target_workspace_path=}, {id_mapping_path=}"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
api_base, token = require_rossum_credentials()
|
|
310
|
+
|
|
311
|
+
source_ws = Workspace(Path(source_workspace_path), api_base=api_base, token=token)
|
|
312
|
+
target_ws = Workspace(Path(target_workspace_path), api_base=api_base, token=token)
|
|
313
|
+
|
|
314
|
+
id_mapping = None
|
|
315
|
+
if id_mapping_path:
|
|
316
|
+
with open(id_mapping_path) as f:
|
|
317
|
+
id_mapping = IdMapping.model_validate(json.load(f))
|
|
318
|
+
|
|
319
|
+
result = source_ws.compare_workspaces(target_ws, id_mapping=id_mapping)
|
|
320
|
+
|
|
321
|
+
return json.dumps(
|
|
322
|
+
{
|
|
323
|
+
"status": "success",
|
|
324
|
+
"summary": result.summary(color=False),
|
|
325
|
+
"source_workspace_id": result.source_workspace_id,
|
|
326
|
+
"target_workspace_id": result.target_workspace_id,
|
|
327
|
+
"total_identical": result.total_identical,
|
|
328
|
+
"total_different": result.total_different,
|
|
329
|
+
"source_only_count": len(result.source_only),
|
|
330
|
+
"target_only_count": len(result.target_only),
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.exception("Error in deploy_compare_workspaces")
|
|
335
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@beta_tool
|
|
339
|
+
def deploy_to_org(
|
|
340
|
+
target_org_id: int,
|
|
341
|
+
target_api_base: str | None = None,
|
|
342
|
+
target_token: str | None = None,
|
|
343
|
+
dry_run: bool = False,
|
|
344
|
+
workspace_path: str | None = None,
|
|
345
|
+
) -> str:
|
|
346
|
+
"""Deploy local configuration changes to a target organization.
|
|
347
|
+
|
|
348
|
+
Uses saved ID mappings from copy_org to update the corresponding objects
|
|
349
|
+
in the target organization. This is the final step in the deployment workflow.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
target_org_id: Target organization ID to deploy to.
|
|
353
|
+
target_api_base: Target API base URL if different from source.
|
|
354
|
+
target_token: Target API token if different from source.
|
|
355
|
+
dry_run: If True, only show what would be deployed without making changes.
|
|
356
|
+
workspace_path: Optional path to the workspace directory.
|
|
357
|
+
Defaults to './rossum-config' in the session output directory.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
JSON with deploy summary including counts of created, updated, skipped, and failed objects.
|
|
361
|
+
"""
|
|
362
|
+
logger.info(f"deploy_to_org called with {target_org_id=}, {target_api_base=}, {dry_run=}, {workspace_path=}")
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
ws = create_workspace(workspace_path)
|
|
366
|
+
|
|
367
|
+
result = ws.deploy(
|
|
368
|
+
target_org_id=target_org_id, target_api_base=target_api_base, target_token=target_token, dry_run=dry_run
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
return json.dumps(
|
|
372
|
+
{
|
|
373
|
+
"status": "success",
|
|
374
|
+
"dry_run": dry_run,
|
|
375
|
+
"summary": result.summary(),
|
|
376
|
+
"created_count": len(result.created),
|
|
377
|
+
"updated_count": len(result.updated),
|
|
378
|
+
"skipped_count": len(result.skipped),
|
|
379
|
+
"failed_count": len(result.failed),
|
|
380
|
+
"workspace_path": str(ws.path),
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
except Exception as e:
|
|
384
|
+
logger.exception("Error in deploy_to_org")
|
|
385
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
DEPLOY_TOOLS: list[BetaTool[..., str]] = [
|
|
389
|
+
deploy_pull,
|
|
390
|
+
deploy_diff,
|
|
391
|
+
deploy_push,
|
|
392
|
+
deploy_copy_org,
|
|
393
|
+
deploy_copy_workspace,
|
|
394
|
+
deploy_compare_workspaces,
|
|
395
|
+
deploy_to_org,
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def get_deploy_tools() -> list[ToolParam]:
|
|
400
|
+
return [tool.to_dict() for tool in DEPLOY_TOOLS]
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def get_deploy_tool_names() -> set[str]:
|
|
404
|
+
return {tool.name for tool in DEPLOY_TOOLS}
|