pygeai 0.6.0b7__py3-none-any.whl → 0.6.0b10__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.
- pygeai/_docs/source/conf.py +78 -6
- pygeai/_docs/source/content/api_reference/embeddings.rst +31 -1
- pygeai/_docs/source/content/api_reference/evaluation.rst +590 -0
- pygeai/_docs/source/content/api_reference/feedback.rst +237 -0
- pygeai/_docs/source/content/api_reference/files.rst +592 -0
- pygeai/_docs/source/content/api_reference/gam.rst +401 -0
- pygeai/_docs/source/content/api_reference/proxy.rst +318 -0
- pygeai/_docs/source/content/api_reference/secrets.rst +495 -0
- pygeai/_docs/source/content/api_reference/usage_limits.rst +390 -0
- pygeai/_docs/source/content/api_reference.rst +7 -0
- pygeai/_docs/source/content/debugger.rst +376 -83
- pygeai/_docs/source/content/migration.rst +528 -0
- pygeai/_docs/source/content/modules.rst +1 -1
- pygeai/_docs/source/pygeai.cli.rst +8 -0
- pygeai/_docs/source/pygeai.tests.cli.rst +16 -0
- pygeai/_docs/source/pygeai.tests.core.embeddings.rst +16 -0
- pygeai/_docs/source/pygeai.tests.snippets.chat.rst +40 -0
- pygeai/_docs/source/pygeai.tests.snippets.dbg.rst +45 -0
- pygeai/_docs/source/pygeai.tests.snippets.embeddings.rst +40 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.dataset.rst +197 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.plan.rst +133 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.result.rst +37 -0
- pygeai/_docs/source/pygeai.tests.snippets.evaluation.rst +10 -0
- pygeai/_docs/source/pygeai.tests.snippets.rst +1 -0
- pygeai/admin/clients.py +5 -0
- pygeai/assistant/clients.py +7 -0
- pygeai/assistant/data_analyst/clients.py +2 -0
- pygeai/assistant/rag/clients.py +11 -0
- pygeai/chat/clients.py +191 -25
- pygeai/chat/endpoints.py +2 -1
- pygeai/cli/commands/chat.py +227 -1
- pygeai/cli/commands/embeddings.py +56 -8
- pygeai/cli/commands/migrate.py +994 -434
- pygeai/cli/error_handler.py +116 -0
- pygeai/cli/geai.py +28 -10
- pygeai/cli/parsers.py +8 -2
- pygeai/core/base/clients.py +3 -1
- pygeai/core/common/exceptions.py +11 -10
- pygeai/core/embeddings/__init__.py +19 -0
- pygeai/core/embeddings/clients.py +17 -2
- pygeai/core/embeddings/mappers.py +16 -2
- pygeai/core/embeddings/responses.py +9 -2
- pygeai/core/feedback/clients.py +1 -0
- pygeai/core/files/clients.py +5 -7
- pygeai/core/files/managers.py +42 -0
- pygeai/core/llm/clients.py +4 -0
- pygeai/core/plugins/clients.py +1 -0
- pygeai/core/rerank/clients.py +1 -0
- pygeai/core/secrets/clients.py +6 -0
- pygeai/core/services/rest.py +1 -1
- pygeai/dbg/__init__.py +3 -0
- pygeai/dbg/debugger.py +565 -70
- pygeai/evaluation/clients.py +1 -1
- pygeai/evaluation/dataset/clients.py +45 -44
- pygeai/evaluation/plan/clients.py +27 -26
- pygeai/evaluation/result/clients.py +37 -5
- pygeai/gam/clients.py +4 -0
- pygeai/health/clients.py +1 -0
- pygeai/lab/agents/clients.py +8 -1
- pygeai/lab/models.py +3 -3
- pygeai/lab/processes/clients.py +21 -0
- pygeai/lab/strategies/clients.py +4 -0
- pygeai/lab/tools/clients.py +1 -0
- pygeai/migration/__init__.py +31 -0
- pygeai/migration/strategies.py +404 -155
- pygeai/migration/tools.py +170 -3
- pygeai/organization/clients.py +13 -0
- pygeai/organization/limits/clients.py +15 -0
- pygeai/proxy/clients.py +3 -1
- pygeai/tests/admin/test_clients.py +16 -11
- pygeai/tests/assistants/rag/test_clients.py +35 -23
- pygeai/tests/assistants/test_clients.py +22 -15
- pygeai/tests/auth/test_clients.py +14 -6
- pygeai/tests/chat/test_clients.py +211 -1
- pygeai/tests/cli/commands/test_embeddings.py +32 -9
- pygeai/tests/cli/commands/test_evaluation.py +7 -0
- pygeai/tests/cli/commands/test_migrate.py +112 -243
- pygeai/tests/cli/test_error_handler.py +225 -0
- pygeai/tests/cli/test_geai_driver.py +154 -0
- pygeai/tests/cli/test_parsers.py +5 -5
- pygeai/tests/core/embeddings/test_clients.py +144 -0
- pygeai/tests/core/embeddings/test_managers.py +171 -0
- pygeai/tests/core/embeddings/test_mappers.py +142 -0
- pygeai/tests/core/feedback/test_clients.py +2 -0
- pygeai/tests/core/files/test_clients.py +1 -0
- pygeai/tests/core/llm/test_clients.py +14 -9
- pygeai/tests/core/plugins/test_clients.py +5 -3
- pygeai/tests/core/rerank/test_clients.py +1 -0
- pygeai/tests/core/secrets/test_clients.py +19 -13
- pygeai/tests/dbg/test_debugger.py +453 -75
- pygeai/tests/evaluation/dataset/test_clients.py +3 -1
- pygeai/tests/evaluation/plan/test_clients.py +4 -2
- pygeai/tests/evaluation/result/test_clients.py +7 -5
- pygeai/tests/gam/test_clients.py +1 -1
- pygeai/tests/health/test_clients.py +1 -0
- pygeai/tests/lab/agents/test_clients.py +9 -0
- pygeai/tests/lab/processes/test_clients.py +36 -0
- pygeai/tests/lab/processes/test_mappers.py +3 -0
- pygeai/tests/lab/strategies/test_clients.py +14 -9
- pygeai/tests/migration/test_strategies.py +45 -218
- pygeai/tests/migration/test_tools.py +133 -9
- pygeai/tests/organization/limits/test_clients.py +17 -0
- pygeai/tests/organization/test_clients.py +22 -0
- pygeai/tests/proxy/test_clients.py +2 -0
- pygeai/tests/proxy/test_integration.py +1 -0
- pygeai/tests/snippets/chat/chat_completion_with_reasoning_effort.py +18 -0
- pygeai/tests/snippets/chat/get_response.py +15 -0
- pygeai/tests/snippets/chat/get_response_streaming.py +20 -0
- pygeai/tests/snippets/chat/get_response_with_files.py +16 -0
- pygeai/tests/snippets/chat/get_response_with_tools.py +36 -0
- pygeai/tests/snippets/dbg/__init__.py +0 -0
- pygeai/tests/snippets/dbg/basic_debugging.py +32 -0
- pygeai/tests/snippets/dbg/breakpoint_management.py +48 -0
- pygeai/tests/snippets/dbg/stack_navigation.py +45 -0
- pygeai/tests/snippets/dbg/stepping_example.py +40 -0
- pygeai/tests/snippets/embeddings/cache_example.py +31 -0
- pygeai/tests/snippets/embeddings/cohere_example.py +41 -0
- pygeai/tests/snippets/embeddings/openai_base64_example.py +27 -0
- pygeai/tests/snippets/embeddings/openai_example.py +30 -0
- pygeai/tests/snippets/embeddings/similarity_example.py +42 -0
- pygeai/tests/snippets/evaluation/dataset/__init__.py +0 -0
- pygeai/tests/snippets/evaluation/dataset/complete_workflow_example.py +195 -0
- pygeai/tests/snippets/evaluation/dataset/create_dataset.py +26 -0
- pygeai/tests/snippets/evaluation/dataset/create_dataset_from_file.py +11 -0
- pygeai/tests/snippets/evaluation/dataset/create_dataset_row.py +17 -0
- pygeai/tests/snippets/evaluation/dataset/create_expected_source.py +18 -0
- pygeai/tests/snippets/evaluation/dataset/create_filter_variable.py +19 -0
- pygeai/tests/snippets/evaluation/dataset/delete_dataset.py +9 -0
- pygeai/tests/snippets/evaluation/dataset/delete_dataset_row.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/delete_expected_source.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/delete_filter_variable.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/get_dataset.py +9 -0
- pygeai/tests/snippets/evaluation/dataset/get_dataset_row.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/get_expected_source.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/get_filter_variable.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/list_dataset_rows.py +9 -0
- pygeai/tests/snippets/evaluation/dataset/list_datasets.py +6 -0
- pygeai/tests/snippets/evaluation/dataset/list_expected_sources.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/list_filter_variables.py +10 -0
- pygeai/tests/snippets/evaluation/dataset/update_dataset.py +15 -0
- pygeai/tests/snippets/evaluation/dataset/update_dataset_row.py +20 -0
- pygeai/tests/snippets/evaluation/dataset/update_expected_source.py +18 -0
- pygeai/tests/snippets/evaluation/dataset/update_filter_variable.py +19 -0
- pygeai/tests/snippets/evaluation/dataset/upload_dataset_rows_file.py +10 -0
- pygeai/tests/snippets/evaluation/plan/__init__.py +0 -0
- pygeai/tests/snippets/evaluation/plan/add_plan_system_metric.py +13 -0
- pygeai/tests/snippets/evaluation/plan/complete_workflow_example.py +136 -0
- pygeai/tests/snippets/evaluation/plan/create_evaluation_plan.py +24 -0
- pygeai/tests/snippets/evaluation/plan/create_rag_evaluation_plan.py +22 -0
- pygeai/tests/snippets/evaluation/plan/delete_evaluation_plan.py +9 -0
- pygeai/tests/snippets/evaluation/plan/delete_plan_system_metric.py +13 -0
- pygeai/tests/snippets/evaluation/plan/execute_evaluation_plan.py +11 -0
- pygeai/tests/snippets/evaluation/plan/get_evaluation_plan.py +9 -0
- pygeai/tests/snippets/evaluation/plan/get_plan_system_metric.py +13 -0
- pygeai/tests/snippets/evaluation/plan/get_system_metric.py +9 -0
- pygeai/tests/snippets/evaluation/plan/list_evaluation_plans.py +7 -0
- pygeai/tests/snippets/evaluation/plan/list_plan_system_metrics.py +9 -0
- pygeai/tests/snippets/evaluation/plan/list_system_metrics.py +7 -0
- pygeai/tests/snippets/evaluation/plan/update_evaluation_plan.py +22 -0
- pygeai/tests/snippets/evaluation/plan/update_plan_system_metric.py +14 -0
- pygeai/tests/snippets/evaluation/result/__init__.py +0 -0
- pygeai/tests/snippets/evaluation/result/complete_workflow_example.py +150 -0
- pygeai/tests/snippets/evaluation/result/get_evaluation_result.py +26 -0
- pygeai/tests/snippets/evaluation/result/list_evaluation_results.py +17 -0
- pygeai/tests/snippets/migrate/__init__.py +45 -0
- pygeai/tests/snippets/migrate/agent_migration.py +110 -0
- pygeai/tests/snippets/migrate/assistant_migration.py +64 -0
- pygeai/tests/snippets/migrate/orchestrator_examples.py +179 -0
- pygeai/tests/snippets/migrate/process_migration.py +64 -0
- pygeai/tests/snippets/migrate/project_migration.py +42 -0
- pygeai/tests/snippets/migrate/tool_migration.py +64 -0
- pygeai/tests/snippets/organization/create_project.py +2 -2
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/METADATA +1 -1
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/RECORD +178 -96
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/WHEEL +0 -0
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/entry_points.txt +0 -0
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/licenses/LICENSE +0 -0
- {pygeai-0.6.0b7.dist-info → pygeai-0.6.0b10.dist-info}/top_level.txt +0 -0
pygeai/cli/commands/migrate.py
CHANGED
|
@@ -1,515 +1,1112 @@
|
|
|
1
|
+
from typing import Tuple, Dict, Any, Optional, List
|
|
2
|
+
from pygeai import logger
|
|
1
3
|
from pygeai.cli.commands import Command, Option, ArgumentsEnum
|
|
2
4
|
from pygeai.cli.commands.builders import build_help_text
|
|
3
5
|
from pygeai.cli.texts.help import MIGRATE_HELP_TEXT
|
|
4
6
|
from pygeai.core.common.exceptions import MissingRequirementException
|
|
5
7
|
from pygeai.core.utils.console import Console
|
|
6
|
-
from pygeai.
|
|
7
|
-
|
|
8
|
-
from pygeai.
|
|
8
|
+
from pygeai.lab.managers import AILabManager
|
|
9
|
+
from pygeai.lab.models import FilterSettings
|
|
10
|
+
from pygeai.assistant.managers import AssistantManager
|
|
11
|
+
from pygeai.assistant.rag.clients import RAGAssistantClient
|
|
12
|
+
from pygeai.assistant.rag.mappers import RAGAssistantMapper
|
|
13
|
+
from pygeai.core.files.managers import FileManager
|
|
14
|
+
from pygeai.migration.strategies import (
|
|
15
|
+
ProjectMigrationStrategy,
|
|
16
|
+
AgentMigrationStrategy,
|
|
17
|
+
ToolMigrationStrategy,
|
|
18
|
+
AgenticProcessMigrationStrategy,
|
|
19
|
+
TaskMigrationStrategy,
|
|
20
|
+
UsageLimitMigrationStrategy,
|
|
21
|
+
RAGAssistantMigrationStrategy,
|
|
22
|
+
FileMigrationStrategy
|
|
23
|
+
)
|
|
24
|
+
from pygeai.migration.tools import MigrationTool, MigrationPlan, MigrationOrchestrator
|
|
25
|
+
from pygeai.admin.clients import AdminClient
|
|
9
26
|
|
|
10
27
|
|
|
11
|
-
def show_help():
|
|
28
|
+
def show_help() -> None:
|
|
12
29
|
"""
|
|
13
|
-
Displays help text in stdout
|
|
30
|
+
Displays help text in stdout.
|
|
14
31
|
"""
|
|
15
32
|
help_text = build_help_text(migrate_commands, MIGRATE_HELP_TEXT)
|
|
16
33
|
Console.write_stdout(help_text)
|
|
17
34
|
|
|
18
35
|
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
def prompt_with_retry(
|
|
37
|
+
prompt_message: str,
|
|
38
|
+
valid_choices: Optional[list] = None,
|
|
39
|
+
allow_empty: bool = False
|
|
40
|
+
) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Prompt user for input with validation and retry logic.
|
|
43
|
+
|
|
44
|
+
:param prompt_message: Message to display when prompting
|
|
45
|
+
:param valid_choices: Optional list of valid input choices
|
|
46
|
+
:param allow_empty: Whether to allow empty input
|
|
47
|
+
:return: User's validated input string
|
|
48
|
+
"""
|
|
49
|
+
while True:
|
|
50
|
+
user_input = input(prompt_message).strip()
|
|
51
|
+
|
|
52
|
+
if not user_input and not allow_empty:
|
|
53
|
+
Console.write_stdout("Error: Input cannot be empty. Please try again.")
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
if valid_choices and user_input not in valid_choices:
|
|
57
|
+
Console.write_stdout(f"Error: Invalid choice '{user_input}'. Valid options: {', '.join(valid_choices)}")
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
return user_input
|
|
24
61
|
|
|
25
|
-
for option_flag, option_arg in option_list:
|
|
26
|
-
if option_flag.name == "from_api_key":
|
|
27
|
-
from_api_key = option_arg
|
|
28
|
-
if option_flag.name == "from_instance":
|
|
29
|
-
from_instance = option_arg
|
|
30
|
-
if option_flag.name == "to_api_key":
|
|
31
|
-
to_api_key = option_arg
|
|
32
|
-
if option_flag.name == "to_instance":
|
|
33
|
-
to_instance = option_arg
|
|
34
62
|
|
|
35
|
-
|
|
36
|
-
|
|
63
|
+
def prompt_resource_selection(
|
|
64
|
+
resource_type: str,
|
|
65
|
+
items: list,
|
|
66
|
+
id_field: str = "id",
|
|
67
|
+
name_field: str = "name"
|
|
68
|
+
) -> Optional[str]:
|
|
69
|
+
"""
|
|
70
|
+
Display a list of resources and let the user select which ones to migrate.
|
|
71
|
+
|
|
72
|
+
:param resource_type: Type of resource being selected (for display purposes)
|
|
73
|
+
:param items: List of resource items to display
|
|
74
|
+
:param id_field: Name of the attribute containing the resource ID
|
|
75
|
+
:param name_field: Name of the attribute containing the resource name
|
|
76
|
+
:return: Comma-separated string of selected IDs, 'all' for all resources, or None to cancel
|
|
77
|
+
"""
|
|
78
|
+
if not items:
|
|
79
|
+
Console.write_stdout(f"No {resource_type} found.")
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
Console.write_stdout(f"\nAvailable {resource_type}:")
|
|
83
|
+
Console.write_stdout(" 0. Cancel (don't migrate this resource type)")
|
|
84
|
+
|
|
85
|
+
for idx, item in enumerate(items, 1):
|
|
86
|
+
item_id = getattr(item, id_field, None)
|
|
87
|
+
item_name = getattr(item, name_field, None) if hasattr(item, name_field) else None
|
|
88
|
+
if item_name:
|
|
89
|
+
Console.write_stdout(f" {idx}. {item_name} (ID: {item_id})")
|
|
90
|
+
else:
|
|
91
|
+
Console.write_stdout(f" {idx}. {item_id}")
|
|
92
|
+
|
|
93
|
+
while True:
|
|
94
|
+
selection = input(f"\nSelect {resource_type} (comma-separated numbers, or empty for all): ").strip()
|
|
95
|
+
|
|
96
|
+
if not selection:
|
|
97
|
+
return "all"
|
|
98
|
+
|
|
99
|
+
if selection == "0":
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
indices = [int(x.strip()) for x in selection.split(",")]
|
|
104
|
+
if any(i < 0 or i > len(items) for i in indices):
|
|
105
|
+
Console.write_stdout(f"Error: Invalid selection. Numbers must be between 0 and {len(items)}.")
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if 0 in indices:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
selected_ids = [getattr(items[i-1], id_field) for i in indices]
|
|
112
|
+
return ",".join(str(sid) for sid in selected_ids)
|
|
113
|
+
except (ValueError, IndexError) as e:
|
|
114
|
+
Console.write_stdout(f"Error: Invalid input format. Please enter comma-separated numbers.")
|
|
115
|
+
continue
|
|
37
116
|
|
|
38
|
-
return from_api_key, from_instance, to_api_key, to_instance
|
|
39
117
|
|
|
118
|
+
def get_source_configuration() -> Tuple[str, str, str, Optional[str]]:
|
|
119
|
+
"""
|
|
120
|
+
Prompt user for source configuration and retrieve organization ID.
|
|
121
|
+
|
|
122
|
+
:return: Tuple of (api_key, instance_url, project_id, organization_id)
|
|
123
|
+
"""
|
|
124
|
+
Console.write_stdout("\n--- Source Configuration ---")
|
|
125
|
+
from_api_key = prompt_with_retry("Source API key: ")
|
|
126
|
+
from_instance = prompt_with_retry("Source instance URL: ")
|
|
127
|
+
from_project_id = prompt_with_retry("Source project ID: ")
|
|
128
|
+
|
|
129
|
+
admin_client = AdminClient(api_key=from_api_key, base_url=from_instance)
|
|
130
|
+
source_token_info = admin_client.validate_api_token()
|
|
131
|
+
from_organization_id = source_token_info.get("organizationId")
|
|
132
|
+
|
|
133
|
+
return from_api_key, from_instance, from_project_id, from_organization_id
|
|
40
134
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"Destination
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
135
|
+
|
|
136
|
+
def get_destination_configuration(
|
|
137
|
+
same_instance: bool,
|
|
138
|
+
from_instance: str,
|
|
139
|
+
from_api_key: str,
|
|
140
|
+
from_organization_id: Optional[str],
|
|
141
|
+
creating_project: bool
|
|
142
|
+
) -> Tuple[str, Optional[str], Optional[str]]:
|
|
143
|
+
"""
|
|
144
|
+
Configure destination instance settings based on migration type.
|
|
145
|
+
|
|
146
|
+
:param same_instance: Whether migration is within the same instance
|
|
147
|
+
:param from_instance: Source instance URL
|
|
148
|
+
:param from_api_key: Source API key
|
|
149
|
+
:param from_organization_id: Source organization ID
|
|
150
|
+
:param creating_project: Whether a new project will be created
|
|
151
|
+
:return: Tuple of (instance_url, api_key, organization_id)
|
|
152
|
+
"""
|
|
153
|
+
if same_instance:
|
|
154
|
+
to_instance = from_instance
|
|
155
|
+
to_organization_id = from_organization_id
|
|
156
|
+
Console.write_stdout(f"Destination instance: {to_instance} (same as source)")
|
|
157
|
+
Console.write_stdout(f"Destination organization ID: {to_organization_id} (same as source)")
|
|
158
|
+
|
|
159
|
+
if creating_project:
|
|
160
|
+
to_api_key = None
|
|
161
|
+
Console.write_stdout("Destination API key: (will be created after project creation)")
|
|
162
|
+
else:
|
|
163
|
+
to_api_key = prompt_with_retry("Destination API key: ")
|
|
164
|
+
else:
|
|
165
|
+
Console.write_stdout("\n--- Destination Configuration ---")
|
|
166
|
+
to_instance = prompt_with_retry("Destination instance URL: ")
|
|
167
|
+
|
|
168
|
+
if creating_project:
|
|
169
|
+
to_api_key = None
|
|
170
|
+
to_organization_id = None
|
|
171
|
+
Console.write_stdout("Destination API key: (will be created after project creation)")
|
|
172
|
+
Console.write_stdout("Destination organization ID: (will be retrieved after project creation)")
|
|
173
|
+
else:
|
|
174
|
+
to_api_key = prompt_with_retry("Destination API key: ")
|
|
175
|
+
dest_admin_client = AdminClient(api_key=to_api_key, base_url=to_instance)
|
|
176
|
+
dest_token_info = dest_admin_client.validate_api_token()
|
|
177
|
+
to_organization_id = dest_token_info.get("organizationId")
|
|
178
|
+
|
|
179
|
+
return to_instance, to_api_key, to_organization_id
|
|
67
180
|
|
|
68
181
|
|
|
69
|
-
def
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
182
|
+
def get_project_creation_info(same_instance: bool) -> Tuple[Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]]:
|
|
183
|
+
"""
|
|
184
|
+
Prompt user for project creation information.
|
|
185
|
+
|
|
186
|
+
:param same_instance: Whether migration is within the same instance
|
|
187
|
+
:return: Tuple of (from_org_api_key, to_org_api_key, project_name, admin_email, project_id)
|
|
188
|
+
"""
|
|
189
|
+
create_project = prompt_with_retry(
|
|
190
|
+
"Create new destination project? (y/n): ",
|
|
191
|
+
valid_choices=["y", "n"]
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
from_organization_api_key = None
|
|
195
|
+
to_organization_api_key = None
|
|
74
196
|
to_project_name = None
|
|
75
|
-
to_instance = None
|
|
76
197
|
admin_email = None
|
|
198
|
+
to_project_id = None
|
|
199
|
+
|
|
200
|
+
if create_project == "y":
|
|
201
|
+
from_organization_api_key = prompt_with_retry("Source organization API key: ")
|
|
202
|
+
if same_instance:
|
|
203
|
+
to_organization_api_key = from_organization_api_key
|
|
204
|
+
Console.write_stdout("Destination organization API key: (same as source)")
|
|
205
|
+
else:
|
|
206
|
+
to_organization_api_key = prompt_with_retry("Destination organization API key: ")
|
|
207
|
+
to_project_name = prompt_with_retry("New project name: ")
|
|
208
|
+
admin_email = prompt_with_retry("Admin email: ")
|
|
209
|
+
else:
|
|
210
|
+
to_project_id = prompt_with_retry("Destination project ID: ")
|
|
211
|
+
|
|
212
|
+
return from_organization_api_key, to_organization_api_key, to_project_name, admin_email, to_project_id
|
|
77
213
|
|
|
78
|
-
for option_flag, option_arg in option_list:
|
|
79
|
-
if option_flag.name == "from_api_key":
|
|
80
|
-
from_api_key = option_arg
|
|
81
|
-
if option_flag.name == "from_project_id":
|
|
82
|
-
from_project_id = option_arg
|
|
83
|
-
if option_flag.name == "from_instance":
|
|
84
|
-
from_instance = option_arg
|
|
85
|
-
if option_flag.name == "to_api_key":
|
|
86
|
-
to_api_key = option_arg
|
|
87
|
-
if option_flag.name == "to_project_name":
|
|
88
|
-
to_project_name = option_arg
|
|
89
|
-
if option_flag.name == "to_instance":
|
|
90
|
-
to_instance = option_arg
|
|
91
|
-
if option_flag.name == "admin_email":
|
|
92
|
-
admin_email = option_arg
|
|
93
214
|
|
|
94
|
-
|
|
95
|
-
|
|
215
|
+
def select_resource_types() -> List[int]:
|
|
216
|
+
"""
|
|
217
|
+
Prompt user to select which resource types to migrate.
|
|
218
|
+
|
|
219
|
+
:return: List of integers representing selected resource types (1-7)
|
|
220
|
+
"""
|
|
221
|
+
Console.write_stdout("\n--- Resource Type Selection ---")
|
|
222
|
+
Console.write_stdout("Which resource types do you want to migrate?")
|
|
223
|
+
Console.write_stdout(" 1. Agents")
|
|
224
|
+
Console.write_stdout(" 2. Tools")
|
|
225
|
+
Console.write_stdout(" 3. Agentic Processes")
|
|
226
|
+
Console.write_stdout(" 4. Tasks")
|
|
227
|
+
Console.write_stdout(" 5. RAG Assistants")
|
|
228
|
+
Console.write_stdout(" 6. Files")
|
|
229
|
+
Console.write_stdout(" 7. Usage Limits")
|
|
230
|
+
|
|
231
|
+
while True:
|
|
232
|
+
resource_choice = input("\nSelect resource types (comma-separated numbers, or empty for all): ").strip()
|
|
233
|
+
if not resource_choice:
|
|
234
|
+
return [1, 2, 3, 4, 5, 6, 7]
|
|
235
|
+
try:
|
|
236
|
+
resource_types = [int(x.strip()) for x in resource_choice.split(",")]
|
|
237
|
+
if any(i < 1 or i > 7 for i in resource_types):
|
|
238
|
+
Console.write_stdout("Error: Invalid selection. Numbers must be between 1 and 7.")
|
|
239
|
+
continue
|
|
240
|
+
return resource_types
|
|
241
|
+
except ValueError:
|
|
242
|
+
Console.write_stdout("Error: Invalid input format. Please enter comma-separated numbers.")
|
|
243
|
+
continue
|
|
96
244
|
|
|
97
|
-
if not admin_email:
|
|
98
|
-
raise MissingRequirementException("Admin email for new project must be defined.")
|
|
99
245
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
246
|
+
def fetch_and_select_agents(lab_manager: AILabManager) -> Optional[str]:
|
|
247
|
+
"""
|
|
248
|
+
Fetch and prompt user to select agents for migration.
|
|
249
|
+
|
|
250
|
+
:param lab_manager: AI Lab manager instance
|
|
251
|
+
:return: Comma-separated IDs, 'all', or None
|
|
252
|
+
"""
|
|
253
|
+
try:
|
|
254
|
+
agent_list = lab_manager.get_agent_list(FilterSettings(count=1000))
|
|
255
|
+
agents = [a for a in agent_list.agents if a.id]
|
|
256
|
+
if agents:
|
|
257
|
+
selection = prompt_resource_selection("agents", agents, id_field="id", name_field="name")
|
|
258
|
+
return selection
|
|
259
|
+
except Exception as e:
|
|
260
|
+
Console.write_stdout(f"Warning: Could not retrieve agents: {e}")
|
|
261
|
+
return None
|
|
111
262
|
|
|
112
|
-
Console.write_stdout(f"Migration result: \n{response}")
|
|
113
263
|
|
|
264
|
+
def fetch_and_select_tools(lab_manager: AILabManager) -> Optional[str]:
|
|
265
|
+
"""
|
|
266
|
+
Fetch and prompt user to select tools for migration.
|
|
267
|
+
|
|
268
|
+
:param lab_manager: AI Lab manager instance
|
|
269
|
+
:return: Comma-separated IDs, 'all', or None
|
|
270
|
+
"""
|
|
271
|
+
try:
|
|
272
|
+
tool_list = lab_manager.list_tools(FilterSettings(count=1000))
|
|
273
|
+
tools = [t for t in tool_list.tools if t.id]
|
|
274
|
+
if tools:
|
|
275
|
+
selection = prompt_resource_selection("tools", tools, id_field="id", name_field="name")
|
|
276
|
+
return selection
|
|
277
|
+
except Exception as e:
|
|
278
|
+
Console.write_stdout(f"Warning: Could not retrieve tools: {e}")
|
|
279
|
+
return None
|
|
114
280
|
|
|
115
|
-
clone_project_options = [
|
|
116
|
-
Option(
|
|
117
|
-
"from_api_key",
|
|
118
|
-
["--from-api-key", "--fak"],
|
|
119
|
-
"API key for the source instance",
|
|
120
|
-
True
|
|
121
|
-
),
|
|
122
|
-
Option(
|
|
123
|
-
"from_project_id",
|
|
124
|
-
["--from-project-id", "--fpid"],
|
|
125
|
-
"ID of the source project to migrate from",
|
|
126
|
-
True
|
|
127
|
-
),
|
|
128
|
-
Option(
|
|
129
|
-
"from_instance",
|
|
130
|
-
["--from-instance", "--fi"],
|
|
131
|
-
"URL from the source instance to migrate from",
|
|
132
|
-
True
|
|
133
|
-
),
|
|
134
|
-
Option(
|
|
135
|
-
"to_api_key",
|
|
136
|
-
["--to-api-key", "--tak"],
|
|
137
|
-
"API key for the destination instance. If not specified, the same instance's API key will be used",
|
|
138
|
-
True
|
|
139
|
-
),
|
|
140
|
-
Option(
|
|
141
|
-
"to_project_name",
|
|
142
|
-
["--to-project-name", "--tpn"],
|
|
143
|
-
"Name of the destination project to migrate to",
|
|
144
|
-
True
|
|
145
|
-
),
|
|
146
|
-
Option(
|
|
147
|
-
"to_instance",
|
|
148
|
-
["--to-instance", "--ti"],
|
|
149
|
-
"URL from the destination instance to migrate to. If not specified, the same instance's URL will be used",
|
|
150
|
-
True
|
|
151
|
-
),
|
|
152
|
-
Option(
|
|
153
|
-
"admin_email",
|
|
154
|
-
["--admin-email", "--ae"],
|
|
155
|
-
"Email from destination project's administrator",
|
|
156
|
-
True
|
|
157
|
-
),
|
|
158
|
-
]
|
|
159
281
|
|
|
282
|
+
def fetch_and_select_processes(lab_manager: AILabManager) -> Optional[str]:
|
|
283
|
+
"""
|
|
284
|
+
Fetch and prompt user to select processes for migration.
|
|
285
|
+
|
|
286
|
+
:param lab_manager: AI Lab manager instance
|
|
287
|
+
:return: Comma-separated IDs, 'all', or None
|
|
288
|
+
"""
|
|
289
|
+
try:
|
|
290
|
+
process_list = lab_manager.list_processes(FilterSettings(count=1000))
|
|
291
|
+
processes = [p for p in process_list.processes if p.id]
|
|
292
|
+
if processes:
|
|
293
|
+
selection = prompt_resource_selection("agentic processes", processes, id_field="id", name_field="name")
|
|
294
|
+
return selection
|
|
295
|
+
except Exception as e:
|
|
296
|
+
Console.write_stdout(f"Warning: Could not retrieve agentic processes: {e}")
|
|
297
|
+
return None
|
|
160
298
|
|
|
161
|
-
def clone_agent(option_list: list):
|
|
162
|
-
from_api_key = None
|
|
163
|
-
from_project_id = None
|
|
164
|
-
from_instance = None
|
|
165
|
-
to_api_key = None
|
|
166
|
-
to_project_id = None
|
|
167
|
-
to_instance = None
|
|
168
|
-
agent_id = None
|
|
169
299
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if not (from_project_id and from_instance and agent_id):
|
|
187
|
-
raise MissingRequirementException("Cannot migrate resources without indicating source: project, instance and agent id")
|
|
188
|
-
|
|
189
|
-
migration_strategy = AgentMigrationStrategy(
|
|
190
|
-
from_api_key=from_api_key,
|
|
191
|
-
from_project_id=from_project_id,
|
|
192
|
-
from_instance=from_instance,
|
|
193
|
-
to_api_key=to_api_key,
|
|
194
|
-
to_project_id=to_project_id,
|
|
195
|
-
to_instance=to_instance,
|
|
196
|
-
agent_id=agent_id
|
|
197
|
-
)
|
|
198
|
-
tool = MigrationTool(migration_strategy)
|
|
199
|
-
response = tool.run_migration()
|
|
300
|
+
def fetch_and_select_tasks(lab_manager: AILabManager) -> Optional[str]:
|
|
301
|
+
"""
|
|
302
|
+
Fetch and prompt user to select tasks for migration.
|
|
303
|
+
|
|
304
|
+
:param lab_manager: AI Lab manager instance
|
|
305
|
+
:return: Comma-separated IDs, 'all', or None
|
|
306
|
+
"""
|
|
307
|
+
try:
|
|
308
|
+
task_list = lab_manager.list_tasks(FilterSettings(count=1000))
|
|
309
|
+
tasks = [t for t in task_list.tasks if t.id]
|
|
310
|
+
if tasks:
|
|
311
|
+
selection = prompt_resource_selection("tasks", tasks, id_field="id", name_field="name")
|
|
312
|
+
return selection
|
|
313
|
+
except Exception as e:
|
|
314
|
+
Console.write_stdout(f"Warning: Could not retrieve tasks: {e}")
|
|
315
|
+
return None
|
|
200
316
|
|
|
201
317
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
318
|
+
def fetch_and_select_rag_assistants(from_api_key: str, from_instance: str) -> Optional[str]:
|
|
319
|
+
"""
|
|
320
|
+
Fetch and prompt user to select RAG assistants for migration.
|
|
321
|
+
|
|
322
|
+
:param from_api_key: Source API key
|
|
323
|
+
:param from_instance: Source instance URL
|
|
324
|
+
:return: Comma-separated names, 'all', or None
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
rag_client = RAGAssistantClient(api_key=from_api_key, base_url=from_instance)
|
|
328
|
+
assistant_data = rag_client.get_assistants_from_project()
|
|
329
|
+
assistants_raw = assistant_data.get("assistants", [])
|
|
330
|
+
assistant_list = [RAGAssistantMapper.map_to_rag_assistant(a) for a in assistants_raw] if assistants_raw else []
|
|
331
|
+
if assistant_list:
|
|
332
|
+
selection = prompt_resource_selection("RAG assistants", assistant_list, id_field="name", name_field="name")
|
|
333
|
+
return selection
|
|
334
|
+
except Exception as e:
|
|
335
|
+
Console.write_stdout(f"Warning: Could not retrieve RAG assistants: {e}")
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def fetch_and_select_files(
|
|
340
|
+
from_api_key: str,
|
|
341
|
+
from_instance: str,
|
|
342
|
+
from_organization_id: str,
|
|
343
|
+
from_project_id: str
|
|
344
|
+
) -> Optional[str]:
|
|
345
|
+
"""
|
|
346
|
+
Fetch and prompt user to select files for migration.
|
|
347
|
+
|
|
348
|
+
:param from_api_key: Source API key
|
|
349
|
+
:param from_instance: Source instance URL
|
|
350
|
+
:param from_organization_id: Source organization ID
|
|
351
|
+
:param from_project_id: Source project ID
|
|
352
|
+
:return: Comma-separated file IDs, 'all', or None
|
|
353
|
+
"""
|
|
354
|
+
try:
|
|
355
|
+
file_manager = FileManager(
|
|
356
|
+
api_key=from_api_key,
|
|
357
|
+
base_url=from_instance,
|
|
358
|
+
organization_id=from_organization_id,
|
|
359
|
+
project_id=from_project_id
|
|
360
|
+
)
|
|
361
|
+
file_list_response = file_manager.get_file_list()
|
|
362
|
+
files = [f for f in file_list_response.files if f.id]
|
|
363
|
+
if files:
|
|
364
|
+
selection = prompt_resource_selection("files", files, id_field="id", name_field="filename")
|
|
365
|
+
return selection
|
|
366
|
+
except Exception as e:
|
|
367
|
+
Console.write_stdout(f"Warning: Could not retrieve files: {e}")
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def handle_usage_limits_keys(
|
|
372
|
+
same_instance: bool,
|
|
373
|
+
from_organization_api_key: Optional[str],
|
|
374
|
+
to_organization_api_key: Optional[str]
|
|
375
|
+
) -> Tuple[str, str]:
|
|
376
|
+
"""
|
|
377
|
+
Ensure organization API keys are available for usage limits migration.
|
|
378
|
+
|
|
379
|
+
:param same_instance: Whether migration is within the same instance
|
|
380
|
+
:param from_organization_api_key: Source organization API key (may be None)
|
|
381
|
+
:param to_organization_api_key: Destination organization API key (may be None)
|
|
382
|
+
:return: Tuple of (from_org_api_key, to_org_api_key)
|
|
383
|
+
"""
|
|
384
|
+
if not from_organization_api_key:
|
|
385
|
+
from_organization_api_key = prompt_with_retry("Source organization API key (required for usage limits): ")
|
|
386
|
+
if same_instance:
|
|
387
|
+
to_organization_api_key = from_organization_api_key
|
|
388
|
+
elif not to_organization_api_key:
|
|
389
|
+
to_organization_api_key = prompt_with_retry("Destination organization API key (required for usage limits): ")
|
|
390
|
+
|
|
391
|
+
return from_organization_api_key, to_organization_api_key
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def show_summary_and_confirm(
|
|
395
|
+
from_instance: str,
|
|
396
|
+
from_project_id: str,
|
|
397
|
+
to_instance: str,
|
|
398
|
+
to_project_name: Optional[str],
|
|
399
|
+
to_project_id: Optional[str],
|
|
400
|
+
selected_resources: Dict[str, Any]
|
|
401
|
+
) -> Tuple[bool, bool]:
|
|
402
|
+
"""
|
|
403
|
+
Display migration summary and prompt user for confirmation and error handling preference.
|
|
404
|
+
|
|
405
|
+
:param from_instance: Source instance URL
|
|
406
|
+
:param from_project_id: Source project ID
|
|
407
|
+
:param to_instance: Destination instance URL
|
|
408
|
+
:param to_project_name: Destination project name (if creating new project)
|
|
409
|
+
:param to_project_id: Destination project ID (if using existing project)
|
|
410
|
+
:param selected_resources: Dictionary of selected resources to migrate
|
|
411
|
+
:return: Tuple of (confirmation, stop_on_error) - whether user confirmed migration and whether to stop on errors
|
|
412
|
+
"""
|
|
413
|
+
Console.write_stdout("\n--- Migration Summary ---")
|
|
414
|
+
Console.write_stdout(f"Source: {from_instance} / Project: {from_project_id}")
|
|
415
|
+
Console.write_stdout(f"Destination: {to_instance} / Project: {to_project_name or to_project_id}")
|
|
416
|
+
Console.write_stdout(f"Resources: {', '.join(selected_resources.keys()) if selected_resources else 'None'}")
|
|
417
|
+
Console.write_stdout("")
|
|
418
|
+
|
|
419
|
+
stop_on_error_response = prompt_with_retry("Stop migration on first error? (Y/n): ", valid_choices=["y", "n", "Y", "N", ""])
|
|
420
|
+
stop_on_error = stop_on_error_response.lower() != "n"
|
|
421
|
+
|
|
422
|
+
confirm = prompt_with_retry("Proceed with migration? (y/n): ", valid_choices=["y", "n"])
|
|
423
|
+
return confirm == "y", stop_on_error
|
|
245
424
|
|
|
246
|
-
]
|
|
247
425
|
|
|
426
|
+
def build_option_list_and_execute(
|
|
427
|
+
from_api_key: str,
|
|
428
|
+
from_instance: str,
|
|
429
|
+
from_project_id: str,
|
|
430
|
+
from_organization_id: Optional[str],
|
|
431
|
+
from_organization_api_key: Optional[str],
|
|
432
|
+
to_api_key: Optional[str],
|
|
433
|
+
to_instance: str,
|
|
434
|
+
to_project_id: Optional[str],
|
|
435
|
+
to_organization_id: Optional[str],
|
|
436
|
+
to_organization_api_key: Optional[str],
|
|
437
|
+
to_project_name: Optional[str],
|
|
438
|
+
admin_email: Optional[str],
|
|
439
|
+
selected_resources: Dict[str, Any],
|
|
440
|
+
stop_on_error: bool
|
|
441
|
+
) -> None:
|
|
442
|
+
"""
|
|
443
|
+
Build option list from interactive mode selections and execute migration.
|
|
444
|
+
|
|
445
|
+
:param from_api_key: Source API key
|
|
446
|
+
:param from_instance: Source instance URL
|
|
447
|
+
:param from_project_id: Source project ID
|
|
448
|
+
:param from_organization_id: Source organization ID
|
|
449
|
+
:param from_organization_api_key: Source organization API key
|
|
450
|
+
:param to_api_key: Destination API key
|
|
451
|
+
:param to_instance: Destination instance URL
|
|
452
|
+
:param to_project_id: Destination project ID
|
|
453
|
+
:param to_organization_id: Destination organization ID
|
|
454
|
+
:param to_organization_api_key: Destination organization API key
|
|
455
|
+
:param to_project_name: New project name (if creating)
|
|
456
|
+
:param admin_email: Admin email (if creating project)
|
|
457
|
+
:param selected_resources: Dictionary of selected resources to migrate
|
|
458
|
+
:param stop_on_error: Whether to stop migration on first error
|
|
459
|
+
"""
|
|
460
|
+
option_list = []
|
|
461
|
+
option_list.append((type('obj', (object,), {'name': 'from_api_key'})(), from_api_key))
|
|
462
|
+
option_list.append((type('obj', (object,), {'name': 'from_instance'})(), from_instance))
|
|
463
|
+
option_list.append((type('obj', (object,), {'name': 'from_project_id'})(), from_project_id))
|
|
464
|
+
option_list.append((type('obj', (object,), {'name': 'from_organization_api_key'})(), from_organization_api_key))
|
|
465
|
+
|
|
466
|
+
if from_organization_id:
|
|
467
|
+
option_list.append((type('obj', (object,), {'name': 'from_organization_id'})(), from_organization_id))
|
|
468
|
+
|
|
469
|
+
option_list.append((type('obj', (object,), {'name': 'to_api_key'})(), to_api_key))
|
|
470
|
+
option_list.append((type('obj', (object,), {'name': 'to_instance'})(), to_instance))
|
|
471
|
+
option_list.append((type('obj', (object,), {'name': 'to_organization_api_key'})(), to_organization_api_key))
|
|
472
|
+
|
|
473
|
+
if to_project_id:
|
|
474
|
+
option_list.append((type('obj', (object,), {'name': 'to_project_id'})(), to_project_id))
|
|
475
|
+
if to_project_name:
|
|
476
|
+
option_list.append((type('obj', (object,), {'name': 'to_project_name'})(), to_project_name))
|
|
477
|
+
if admin_email:
|
|
478
|
+
option_list.append((type('obj', (object,), {'name': 'admin_email'})(), admin_email))
|
|
479
|
+
if to_organization_id:
|
|
480
|
+
option_list.append((type('obj', (object,), {'name': 'to_organization_id'})(), to_organization_id))
|
|
481
|
+
|
|
482
|
+
for resource_type, value in selected_resources.items():
|
|
483
|
+
option_list.append((type('obj', (object,), {'name': resource_type})(), value))
|
|
484
|
+
|
|
485
|
+
option_list.append((type('obj', (object,), {'name': 'stop_on_error'})(), "1" if stop_on_error else "0"))
|
|
486
|
+
|
|
487
|
+
clone_project(option_list)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def clone_project_interactively() -> None:
|
|
491
|
+
"""
|
|
492
|
+
Run interactive migration wizard with step-by-step prompts for all configuration.
|
|
493
|
+
"""
|
|
494
|
+
Console.write_stdout("")
|
|
495
|
+
Console.write_stdout("=" * 80)
|
|
496
|
+
Console.write_stdout("PROJECT MIGRATION ASSISTANT")
|
|
497
|
+
Console.write_stdout("=" * 80)
|
|
498
|
+
Console.write_stdout("")
|
|
499
|
+
|
|
500
|
+
migration_type = prompt_with_retry(
|
|
501
|
+
"Migration type (1=same instance, 2=cross instance): ",
|
|
502
|
+
valid_choices=["1", "2"]
|
|
503
|
+
)
|
|
504
|
+
same_instance = migration_type == "1"
|
|
505
|
+
|
|
506
|
+
from_api_key, from_instance, from_project_id, from_organization_id = get_source_configuration()
|
|
507
|
+
from_organization_api_key, to_organization_api_key, to_project_name, admin_email, to_project_id = get_project_creation_info(same_instance)
|
|
508
|
+
|
|
509
|
+
creating_project = bool(to_project_name and admin_email)
|
|
510
|
+
to_instance, to_api_key, to_organization_id = get_destination_configuration(
|
|
511
|
+
same_instance, from_instance, from_api_key, from_organization_id, creating_project
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
resource_types_to_migrate = select_resource_types()
|
|
515
|
+
|
|
516
|
+
Console.write_stdout("\n--- Retrieving Available Resources ---")
|
|
517
|
+
lab_manager = AILabManager(api_key=from_api_key, base_url=from_instance)
|
|
518
|
+
|
|
519
|
+
selected_resources = {}
|
|
520
|
+
|
|
521
|
+
if 1 in resource_types_to_migrate:
|
|
522
|
+
selection = fetch_and_select_agents(lab_manager)
|
|
523
|
+
if selection:
|
|
524
|
+
selected_resources["agents"] = selection
|
|
525
|
+
|
|
526
|
+
if 2 in resource_types_to_migrate:
|
|
527
|
+
selection = fetch_and_select_tools(lab_manager)
|
|
528
|
+
if selection:
|
|
529
|
+
selected_resources["tools"] = selection
|
|
530
|
+
|
|
531
|
+
if 3 in resource_types_to_migrate:
|
|
532
|
+
selection = fetch_and_select_processes(lab_manager)
|
|
533
|
+
if selection:
|
|
534
|
+
selected_resources["agentic_processes"] = selection
|
|
535
|
+
|
|
536
|
+
if 4 in resource_types_to_migrate:
|
|
537
|
+
selection = fetch_and_select_tasks(lab_manager)
|
|
538
|
+
if selection:
|
|
539
|
+
selected_resources["tasks"] = selection
|
|
540
|
+
|
|
541
|
+
if 5 in resource_types_to_migrate:
|
|
542
|
+
selection = fetch_and_select_rag_assistants(from_api_key, from_instance)
|
|
543
|
+
if selection:
|
|
544
|
+
selected_resources["rag_assistants"] = selection
|
|
545
|
+
|
|
546
|
+
if 6 in resource_types_to_migrate:
|
|
547
|
+
selection = fetch_and_select_files(from_api_key, from_instance, from_organization_id, from_project_id)
|
|
548
|
+
if selection:
|
|
549
|
+
selected_resources["files"] = selection
|
|
550
|
+
|
|
551
|
+
if 7 in resource_types_to_migrate:
|
|
552
|
+
from_organization_api_key, to_organization_api_key = handle_usage_limits_keys(
|
|
553
|
+
same_instance, from_organization_api_key, to_organization_api_key
|
|
554
|
+
)
|
|
555
|
+
selected_resources["usage_limits"] = True
|
|
556
|
+
|
|
557
|
+
confirmed, stop_on_error = show_summary_and_confirm(from_instance, from_project_id, to_instance, to_project_name, to_project_id, selected_resources)
|
|
558
|
+
if not confirmed:
|
|
559
|
+
Console.write_stdout("Migration cancelled.")
|
|
560
|
+
return
|
|
561
|
+
|
|
562
|
+
build_option_list_and_execute(
|
|
563
|
+
from_api_key, from_instance, from_project_id, from_organization_id, from_organization_api_key,
|
|
564
|
+
to_api_key, to_instance, to_project_id, to_organization_id, to_organization_api_key,
|
|
565
|
+
to_project_name, admin_email, selected_resources, stop_on_error
|
|
566
|
+
)
|
|
248
567
|
|
|
249
|
-
|
|
568
|
+
|
|
569
|
+
def clone_project(option_list: list) -> None:
|
|
570
|
+
"""
|
|
571
|
+
Clone a project with selected components from source to destination instance.
|
|
572
|
+
|
|
573
|
+
Supports migration of agents, tools, agentic processes, tasks, usage limits,
|
|
574
|
+
RAG assistants, and files between GenExus AI instances.
|
|
575
|
+
|
|
576
|
+
:param option_list: List of (option_flag, option_value) tuples from CLI parsing
|
|
577
|
+
"""
|
|
578
|
+
interactive_mode = False
|
|
579
|
+
for option_flag, option_arg in option_list:
|
|
580
|
+
if option_flag.name == "interactive":
|
|
581
|
+
interactive_mode = True
|
|
582
|
+
break
|
|
583
|
+
|
|
584
|
+
if interactive_mode:
|
|
585
|
+
clone_project_interactively()
|
|
586
|
+
return
|
|
587
|
+
|
|
250
588
|
from_api_key = None
|
|
251
|
-
|
|
589
|
+
from_organization_api_key = None
|
|
252
590
|
from_instance = None
|
|
591
|
+
from_project_id = None
|
|
592
|
+
from_organization_id = None
|
|
253
593
|
to_api_key = None
|
|
254
|
-
|
|
594
|
+
to_organization_api_key = None
|
|
255
595
|
to_instance = None
|
|
256
|
-
|
|
596
|
+
to_project_id = None
|
|
597
|
+
to_organization_id = None
|
|
598
|
+
to_project_name = None
|
|
599
|
+
admin_email = None
|
|
600
|
+
|
|
601
|
+
migrate_all = False
|
|
602
|
+
migrate_agents = False
|
|
603
|
+
migrate_tools = False
|
|
604
|
+
migrate_processes = False
|
|
605
|
+
migrate_tasks = False
|
|
606
|
+
migrate_usage_limits = False
|
|
607
|
+
migrate_rag_assistants = False
|
|
608
|
+
migrate_files = False
|
|
609
|
+
|
|
610
|
+
agent_ids = None
|
|
611
|
+
tool_ids = None
|
|
612
|
+
process_ids = None
|
|
613
|
+
task_ids = None
|
|
614
|
+
assistant_names = None
|
|
615
|
+
file_ids = None
|
|
616
|
+
|
|
617
|
+
stop_on_error = True
|
|
257
618
|
|
|
258
619
|
for option_flag, option_arg in option_list:
|
|
259
620
|
if option_flag.name == "from_api_key":
|
|
260
621
|
from_api_key = option_arg
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
622
|
+
elif option_flag.name == "from_organization_api_key":
|
|
623
|
+
from_organization_api_key = option_arg
|
|
624
|
+
elif option_flag.name == "from_instance":
|
|
264
625
|
from_instance = option_arg
|
|
265
|
-
|
|
626
|
+
elif option_flag.name == "from_project_id":
|
|
627
|
+
from_project_id = option_arg
|
|
628
|
+
elif option_flag.name == "from_organization_id":
|
|
629
|
+
from_organization_id = option_arg
|
|
630
|
+
elif option_flag.name == "to_api_key":
|
|
266
631
|
to_api_key = option_arg
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
632
|
+
elif option_flag.name == "to_organization_api_key":
|
|
633
|
+
to_organization_api_key = option_arg
|
|
634
|
+
elif option_flag.name == "to_instance":
|
|
270
635
|
to_instance = option_arg
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
636
|
+
elif option_flag.name == "to_project_id":
|
|
637
|
+
to_project_id = option_arg
|
|
638
|
+
elif option_flag.name == "to_organization_id":
|
|
639
|
+
to_organization_id = option_arg
|
|
640
|
+
elif option_flag.name == "to_project_name":
|
|
641
|
+
to_project_name = option_arg
|
|
642
|
+
elif option_flag.name == "admin_email":
|
|
643
|
+
admin_email = option_arg
|
|
644
|
+
elif option_flag.name == "all":
|
|
645
|
+
migrate_all = True
|
|
646
|
+
elif option_flag.name == "agents":
|
|
647
|
+
migrate_agents = True
|
|
648
|
+
agent_ids = option_arg if option_arg else "all"
|
|
649
|
+
elif option_flag.name == "tools":
|
|
650
|
+
migrate_tools = True
|
|
651
|
+
tool_ids = option_arg if option_arg else "all"
|
|
652
|
+
elif option_flag.name == "agentic_processes":
|
|
653
|
+
migrate_processes = True
|
|
654
|
+
process_ids = option_arg if option_arg else "all"
|
|
655
|
+
elif option_flag.name == "tasks":
|
|
656
|
+
migrate_tasks = True
|
|
657
|
+
task_ids = option_arg if option_arg else "all"
|
|
658
|
+
elif option_flag.name == "usage_limits":
|
|
659
|
+
migrate_usage_limits = True
|
|
660
|
+
elif option_flag.name == "rag_assistants":
|
|
661
|
+
migrate_rag_assistants = True
|
|
662
|
+
assistant_names = option_arg if option_arg else "all"
|
|
663
|
+
elif option_flag.name == "files":
|
|
664
|
+
migrate_files = True
|
|
665
|
+
file_ids = option_arg if option_arg else "all"
|
|
666
|
+
elif option_flag.name == "stop_on_error":
|
|
667
|
+
from pygeai.cli.commands.common import get_boolean_value
|
|
668
|
+
stop_on_error = get_boolean_value(option_arg)
|
|
669
|
+
|
|
670
|
+
if not all([from_api_key, from_instance, from_project_id]):
|
|
671
|
+
raise MissingRequirementException("Source API key, instance, and project ID are required")
|
|
672
|
+
|
|
673
|
+
if (to_project_name or admin_email) and not (to_project_name and admin_email):
|
|
674
|
+
raise MissingRequirementException(
|
|
675
|
+
"Both --to-project-name and --admin-email are required when creating a new project"
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
if to_project_id and (to_project_name or admin_email):
|
|
679
|
+
raise MissingRequirementException(
|
|
680
|
+
"Cannot specify both --to-project-id and project creation parameters (--to-project-name, --admin-email)"
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
if not to_project_id and not (to_project_name and admin_email):
|
|
684
|
+
raise MissingRequirementException(
|
|
685
|
+
"Must specify either --to-project-id (for existing project) or both --to-project-name and --admin-email (to create new project)"
|
|
686
|
+
)
|
|
687
|
+
|
|
688
|
+
if to_project_id and not to_api_key:
|
|
689
|
+
raise MissingRequirementException(
|
|
690
|
+
"Destination project API key (--to-api-key) is required when migrating to an existing project (--to-project-id)"
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
if to_project_name and admin_email:
|
|
694
|
+
if not from_organization_api_key:
|
|
695
|
+
raise MissingRequirementException(
|
|
696
|
+
"Source organization scope API key (--from-org-key) is required for project creation"
|
|
697
|
+
)
|
|
698
|
+
if not (to_organization_api_key or from_organization_api_key):
|
|
699
|
+
raise MissingRequirementException(
|
|
700
|
+
"Destination organization scope API key (--to-org-key) is required for project creation in a different "
|
|
701
|
+
"instance. Alternatively source organization scope (--from-org-key) can be used if project needs to be "
|
|
702
|
+
"created in the same instance."
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
# Validate organization scope keys for usage limits migration
|
|
706
|
+
if migrate_usage_limits:
|
|
707
|
+
if not from_organization_api_key:
|
|
708
|
+
raise MissingRequirementException(
|
|
709
|
+
"Source organization scope API key (--from-org-key) is required for usage limits migration"
|
|
710
|
+
)
|
|
711
|
+
if not (to_organization_api_key or from_organization_api_key):
|
|
712
|
+
raise MissingRequirementException(
|
|
713
|
+
"Destination organization scope API key (--to-org-key) is required for usage limits migration in a "
|
|
714
|
+
"different instance. Alternatively source organization scope (--from-org-key) can be used if limits "
|
|
715
|
+
"need to be migrated in the same instance."
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
if to_project_name and admin_email:
|
|
719
|
+
Console.write_stdout(f"Creating new project '{to_project_name}'...")
|
|
720
|
+
|
|
721
|
+
org_key_to_use = to_organization_api_key or from_organization_api_key
|
|
722
|
+
logger.debug(f"DEBUG: Preparing to create project with organization API key")
|
|
723
|
+
logger.debug(f" - to_organization_api_key exists: {to_organization_api_key is not None}")
|
|
724
|
+
logger.debug(f" - from_organization_api_key exists: {from_organization_api_key is not None}")
|
|
725
|
+
logger.debug(f" - Using key (first 20 chars): {org_key_to_use[:20] if org_key_to_use else 'None'}...")
|
|
726
|
+
Console.write_stderr(f"DEBUG: Using org key for project creation (first 20 chars): {org_key_to_use[:20] if org_key_to_use else 'None'}...")
|
|
727
|
+
|
|
728
|
+
project_strategy = ProjectMigrationStrategy(
|
|
729
|
+
from_api_key=from_organization_api_key,
|
|
730
|
+
from_instance=from_instance,
|
|
731
|
+
from_project_id=from_project_id,
|
|
732
|
+
to_project_name=to_project_name,
|
|
733
|
+
admin_email=admin_email,
|
|
734
|
+
to_api_key=org_key_to_use,
|
|
735
|
+
to_instance=to_instance
|
|
736
|
+
)
|
|
737
|
+
project_tool = MigrationTool(project_strategy)
|
|
738
|
+
to_project_id = project_tool.run_migration()
|
|
739
|
+
|
|
740
|
+
if not to_project_id:
|
|
741
|
+
raise ValueError("Project creation did not return a project ID")
|
|
742
|
+
|
|
743
|
+
Console.write_stdout(f"Project '{to_project_name}' created successfully with ID: {to_project_id}")
|
|
744
|
+
|
|
745
|
+
from pygeai.auth.clients import AuthClient
|
|
746
|
+
Console.write_stdout(f"Creating project API key for new project...")
|
|
747
|
+
|
|
748
|
+
org_key_for_token_creation = to_organization_api_key or from_organization_api_key
|
|
749
|
+
auth_client = AuthClient(
|
|
750
|
+
api_key=org_key_for_token_creation,
|
|
751
|
+
base_url=to_instance or from_instance
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
token_response = auth_client.create_project_api_token(
|
|
755
|
+
project_id=to_project_id,
|
|
756
|
+
name=f"Migration API Key for {to_project_name}",
|
|
757
|
+
description=f"Auto-generated API key for project migration to {to_project_name}"
|
|
758
|
+
)
|
|
759
|
+
|
|
760
|
+
if not token_response or 'id' not in token_response:
|
|
761
|
+
raise ValueError("Failed to create project API key")
|
|
762
|
+
|
|
763
|
+
to_api_key = token_response['id']
|
|
764
|
+
Console.write_stdout(f"Project API key created successfully")
|
|
765
|
+
|
|
766
|
+
if not to_organization_id:
|
|
767
|
+
Console.write_stdout(f"Retrieving destination organization ID...")
|
|
768
|
+
dest_admin_client = AdminClient(api_key=to_api_key, base_url=to_instance or from_instance)
|
|
769
|
+
dest_token_info = dest_admin_client.validate_api_token()
|
|
770
|
+
to_organization_id = dest_token_info.get("organizationId")
|
|
771
|
+
Console.write_stdout(f"Destination organization ID: {to_organization_id}")
|
|
772
|
+
|
|
773
|
+
if migrate_all:
|
|
774
|
+
migrate_agents = True
|
|
775
|
+
migrate_tools = True
|
|
776
|
+
migrate_processes = True
|
|
777
|
+
migrate_tasks = True
|
|
778
|
+
migrate_usage_limits = True if from_organization_id and to_organization_id else False
|
|
779
|
+
migrate_rag_assistants = True
|
|
780
|
+
migrate_files = True if from_organization_id and to_organization_id else False
|
|
781
|
+
agent_ids = "all"
|
|
782
|
+
tool_ids = "all"
|
|
783
|
+
process_ids = "all"
|
|
784
|
+
task_ids = "all"
|
|
785
|
+
assistant_names = "all"
|
|
786
|
+
file_ids = "all"
|
|
787
|
+
|
|
788
|
+
strategies = []
|
|
789
|
+
|
|
790
|
+
lab_manager = AILabManager(api_key=from_api_key, base_url=from_instance)
|
|
791
|
+
|
|
792
|
+
if migrate_agents:
|
|
793
|
+
if agent_ids == "all":
|
|
794
|
+
agent_list = lab_manager.get_agent_list(FilterSettings(count=1000))
|
|
795
|
+
discovered_agents = [agent.id for agent in agent_list.agents if agent.id]
|
|
796
|
+
Console.write_stdout(f"Discovered {len(discovered_agents)} agents")
|
|
797
|
+
for agent_id in discovered_agents:
|
|
798
|
+
strategies.append(AgentMigrationStrategy(
|
|
799
|
+
from_api_key=from_api_key,
|
|
800
|
+
from_instance=from_instance,
|
|
801
|
+
agent_id=agent_id,
|
|
802
|
+
to_api_key=to_api_key,
|
|
803
|
+
to_instance=to_instance
|
|
804
|
+
))
|
|
805
|
+
elif agent_ids:
|
|
806
|
+
for agent_id in agent_ids.split(','):
|
|
807
|
+
strategies.append(AgentMigrationStrategy(
|
|
808
|
+
from_api_key=from_api_key,
|
|
809
|
+
from_instance=from_instance,
|
|
810
|
+
agent_id=agent_id.strip(),
|
|
811
|
+
to_api_key=to_api_key,
|
|
812
|
+
to_instance=to_instance
|
|
813
|
+
))
|
|
814
|
+
|
|
815
|
+
if migrate_tools:
|
|
816
|
+
if tool_ids == "all":
|
|
817
|
+
tool_list = lab_manager.list_tools(FilterSettings(count=1000))
|
|
818
|
+
discovered_tools = [tool.id for tool in tool_list.tools if tool.id]
|
|
819
|
+
Console.write_stdout(f"Discovered {len(discovered_tools)} tools")
|
|
820
|
+
for tool_id in discovered_tools:
|
|
821
|
+
strategies.append(ToolMigrationStrategy(
|
|
822
|
+
from_api_key=from_api_key,
|
|
823
|
+
from_instance=from_instance,
|
|
824
|
+
tool_id=tool_id,
|
|
825
|
+
to_api_key=to_api_key,
|
|
826
|
+
to_instance=to_instance
|
|
827
|
+
))
|
|
828
|
+
elif tool_ids:
|
|
829
|
+
for tool_id in tool_ids.split(','):
|
|
830
|
+
strategies.append(ToolMigrationStrategy(
|
|
831
|
+
from_api_key=from_api_key,
|
|
832
|
+
from_instance=from_instance,
|
|
833
|
+
tool_id=tool_id.strip(),
|
|
834
|
+
to_api_key=to_api_key,
|
|
835
|
+
to_instance=to_instance
|
|
836
|
+
))
|
|
837
|
+
|
|
838
|
+
if migrate_processes:
|
|
839
|
+
if process_ids == "all":
|
|
840
|
+
process_list = lab_manager.list_processes(FilterSettings(count=1000))
|
|
841
|
+
discovered_processes = [proc.id for proc in process_list.processes if proc.id]
|
|
842
|
+
Console.write_stdout(f"Discovered {len(discovered_processes)} agentic processes")
|
|
843
|
+
for process_id in discovered_processes:
|
|
844
|
+
strategies.append(AgenticProcessMigrationStrategy(
|
|
845
|
+
from_api_key=from_api_key,
|
|
846
|
+
from_instance=from_instance,
|
|
847
|
+
process_id=process_id,
|
|
848
|
+
to_api_key=to_api_key,
|
|
849
|
+
to_instance=to_instance
|
|
850
|
+
))
|
|
851
|
+
elif process_ids:
|
|
852
|
+
for process_id in process_ids.split(','):
|
|
853
|
+
strategies.append(AgenticProcessMigrationStrategy(
|
|
854
|
+
from_api_key=from_api_key,
|
|
855
|
+
from_instance=from_instance,
|
|
856
|
+
process_id=process_id.strip(),
|
|
857
|
+
to_api_key=to_api_key,
|
|
858
|
+
to_instance=to_instance
|
|
859
|
+
))
|
|
860
|
+
|
|
861
|
+
if migrate_tasks:
|
|
862
|
+
if task_ids == "all":
|
|
863
|
+
task_list = lab_manager.list_tasks(FilterSettings(count=1000))
|
|
864
|
+
discovered_tasks = [task.id for task in task_list.tasks if task.id]
|
|
865
|
+
Console.write_stdout(f"Discovered {len(discovered_tasks)} tasks")
|
|
866
|
+
for task_id in discovered_tasks:
|
|
867
|
+
strategies.append(TaskMigrationStrategy(
|
|
868
|
+
from_api_key=from_api_key,
|
|
869
|
+
from_instance=from_instance,
|
|
870
|
+
task_id=task_id,
|
|
871
|
+
to_api_key=to_api_key,
|
|
872
|
+
to_instance=to_instance
|
|
873
|
+
))
|
|
874
|
+
elif task_ids:
|
|
875
|
+
for task_id in task_ids.split(','):
|
|
876
|
+
strategies.append(TaskMigrationStrategy(
|
|
877
|
+
from_api_key=from_api_key,
|
|
878
|
+
from_instance=from_instance,
|
|
879
|
+
task_id=task_id.strip(),
|
|
880
|
+
to_api_key=to_api_key,
|
|
881
|
+
to_instance=to_instance
|
|
882
|
+
))
|
|
883
|
+
|
|
884
|
+
if migrate_usage_limits and from_organization_id and to_organization_id:
|
|
885
|
+
strategies.append(UsageLimitMigrationStrategy(
|
|
886
|
+
from_api_key=from_organization_api_key,
|
|
887
|
+
from_instance=from_instance,
|
|
888
|
+
from_organization_id=from_organization_id,
|
|
889
|
+
to_organization_id=to_organization_id,
|
|
890
|
+
to_api_key=to_organization_api_key or from_organization_api_key,
|
|
891
|
+
to_instance=to_instance
|
|
892
|
+
))
|
|
893
|
+
|
|
894
|
+
if migrate_rag_assistants:
|
|
895
|
+
rag_client = RAGAssistantClient(api_key=from_api_key, base_url=from_instance)
|
|
896
|
+
if assistant_names == "all":
|
|
897
|
+
assistant_data = rag_client.get_assistants_from_project()
|
|
898
|
+
assistants_raw = assistant_data.get("assistants", [])
|
|
899
|
+
assistant_list = [RAGAssistantMapper.map_to_rag_assistant(a) for a in assistants_raw] if assistants_raw else []
|
|
900
|
+
discovered_assistants = [assistant.name for assistant in assistant_list if assistant.name]
|
|
901
|
+
Console.write_stdout(f"Discovered {len(discovered_assistants)} RAG assistants")
|
|
902
|
+
for assistant_name in discovered_assistants:
|
|
903
|
+
strategies.append(RAGAssistantMigrationStrategy(
|
|
904
|
+
from_api_key=from_api_key,
|
|
905
|
+
from_instance=from_instance,
|
|
906
|
+
assistant_name=assistant_name,
|
|
907
|
+
to_api_key=to_api_key,
|
|
908
|
+
to_instance=to_instance
|
|
909
|
+
))
|
|
910
|
+
elif assistant_names:
|
|
911
|
+
for assistant_name in assistant_names.split(','):
|
|
912
|
+
strategies.append(RAGAssistantMigrationStrategy(
|
|
913
|
+
from_api_key=from_api_key,
|
|
914
|
+
from_instance=from_instance,
|
|
915
|
+
assistant_name=assistant_name.strip(),
|
|
916
|
+
to_api_key=to_api_key,
|
|
917
|
+
to_instance=to_instance
|
|
918
|
+
))
|
|
919
|
+
|
|
920
|
+
if migrate_files and from_organization_id and from_project_id and to_organization_id and to_project_id:
|
|
921
|
+
file_manager = FileManager(
|
|
922
|
+
api_key=from_api_key,
|
|
923
|
+
base_url=from_instance,
|
|
924
|
+
organization_id=from_organization_id,
|
|
925
|
+
project_id=from_project_id
|
|
926
|
+
)
|
|
927
|
+
if file_ids == "all":
|
|
928
|
+
file_list_response = file_manager.get_file_list()
|
|
929
|
+
discovered_files = [f.id for f in file_list_response.files if f.id]
|
|
930
|
+
Console.write_stdout(f"Discovered {len(discovered_files)} files")
|
|
931
|
+
for file_id in discovered_files:
|
|
932
|
+
strategies.append(FileMigrationStrategy(
|
|
933
|
+
from_api_key=from_api_key,
|
|
934
|
+
from_instance=from_instance,
|
|
935
|
+
from_organization_id=from_organization_id,
|
|
936
|
+
from_project_id=from_project_id,
|
|
937
|
+
to_organization_id=to_organization_id,
|
|
938
|
+
to_project_id=to_project_id,
|
|
939
|
+
file_id=file_id,
|
|
940
|
+
to_api_key=to_api_key,
|
|
941
|
+
to_instance=to_instance
|
|
942
|
+
))
|
|
943
|
+
elif file_ids:
|
|
944
|
+
for file_id in file_ids.split(','):
|
|
945
|
+
strategies.append(FileMigrationStrategy(
|
|
946
|
+
from_api_key=from_api_key,
|
|
947
|
+
from_instance=from_instance,
|
|
948
|
+
from_organization_id=from_organization_id,
|
|
949
|
+
from_project_id=from_project_id,
|
|
950
|
+
to_organization_id=to_organization_id,
|
|
951
|
+
to_project_id=to_project_id,
|
|
952
|
+
file_id=file_id.strip(),
|
|
953
|
+
to_api_key=to_api_key,
|
|
954
|
+
to_instance=to_instance
|
|
955
|
+
))
|
|
956
|
+
|
|
957
|
+
if not strategies:
|
|
958
|
+
Console.write_stdout("No migration strategies configured. Use flags like --agents, --tools, --all, etc.")
|
|
959
|
+
return
|
|
288
960
|
|
|
961
|
+
plan = MigrationPlan(strategies=strategies, stop_on_error=stop_on_error)
|
|
962
|
+
orchestrator = MigrationOrchestrator(plan)
|
|
963
|
+
|
|
964
|
+
try:
|
|
965
|
+
result = orchestrator.execute()
|
|
966
|
+
Console.write_stdout(f"Migration completed: {result['completed']}/{result['total']} successful")
|
|
967
|
+
logger.info(f"Project cloning completed: {result}")
|
|
968
|
+
except Exception as e:
|
|
969
|
+
Console.write_stderr(f"Migration failed: {e}")
|
|
970
|
+
logger.error(f"Project cloning failed: {e}")
|
|
971
|
+
raise
|
|
289
972
|
|
|
290
|
-
|
|
973
|
+
|
|
974
|
+
clone_project_options = [
|
|
975
|
+
Option(
|
|
976
|
+
"interactive",
|
|
977
|
+
["-i", "--interactive"],
|
|
978
|
+
"Interactive mode: guided step-by-step migration wizard",
|
|
979
|
+
False
|
|
980
|
+
),
|
|
291
981
|
Option(
|
|
292
982
|
"from_api_key",
|
|
293
|
-
["--from-api-key", "--
|
|
294
|
-
"
|
|
983
|
+
["--from-api-key", "--from-key"],
|
|
984
|
+
"Source instance project scope API key (required)",
|
|
295
985
|
True
|
|
296
986
|
),
|
|
297
987
|
Option(
|
|
298
|
-
"
|
|
299
|
-
["--from-
|
|
300
|
-
"
|
|
988
|
+
"from_organization_api_key",
|
|
989
|
+
["--from-org-key", "--from-organization-key"],
|
|
990
|
+
"Source instance organization scope API key (optional, for project creation)",
|
|
301
991
|
True
|
|
302
992
|
),
|
|
303
993
|
Option(
|
|
304
994
|
"from_instance",
|
|
305
|
-
["--from-instance", "--
|
|
306
|
-
"
|
|
995
|
+
["--from-instance", "--from-url"],
|
|
996
|
+
"Source instance URL (required)",
|
|
307
997
|
True
|
|
308
998
|
),
|
|
309
999
|
Option(
|
|
310
|
-
"
|
|
311
|
-
["--
|
|
312
|
-
"
|
|
1000
|
+
"from_project_id",
|
|
1001
|
+
["--from-project-id", "--from-pid"],
|
|
1002
|
+
"Source project ID (required)",
|
|
313
1003
|
True
|
|
314
1004
|
),
|
|
315
1005
|
Option(
|
|
316
|
-
"
|
|
317
|
-
["--
|
|
318
|
-
"ID
|
|
1006
|
+
"from_organization_id",
|
|
1007
|
+
["--from-organization-id", "--from-oid"],
|
|
1008
|
+
"Source organization ID (required for usage limits and files)",
|
|
319
1009
|
True
|
|
320
1010
|
),
|
|
321
1011
|
Option(
|
|
322
|
-
"
|
|
323
|
-
["--to-
|
|
324
|
-
"
|
|
1012
|
+
"to_api_key",
|
|
1013
|
+
["--to-api-key", "--to-key"],
|
|
1014
|
+
"Destination instance project scope API key (optional, defaults to source key)",
|
|
325
1015
|
True
|
|
326
1016
|
),
|
|
327
1017
|
Option(
|
|
328
|
-
"
|
|
329
|
-
["--
|
|
330
|
-
"
|
|
1018
|
+
"to_organization_api_key",
|
|
1019
|
+
["--to-org-key", "--to-organization-key"],
|
|
1020
|
+
"Destination instance organization scope API key (optional, for project creation)",
|
|
331
1021
|
True
|
|
332
1022
|
),
|
|
333
|
-
]
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
def clone_process(option_list: list):
|
|
337
|
-
from_api_key = None
|
|
338
|
-
from_project_id = None
|
|
339
|
-
from_instance = None
|
|
340
|
-
to_api_key = None
|
|
341
|
-
to_project_id = None
|
|
342
|
-
to_instance = None
|
|
343
|
-
process_id = None
|
|
344
|
-
|
|
345
|
-
for option_flag, option_arg in option_list:
|
|
346
|
-
if option_flag.name == "from_api_key":
|
|
347
|
-
from_api_key = option_arg
|
|
348
|
-
if option_flag.name == "from_project_id":
|
|
349
|
-
from_project_id = option_arg
|
|
350
|
-
if option_flag.name == "from_instance":
|
|
351
|
-
from_instance = option_arg
|
|
352
|
-
if option_flag.name == "to_api_key":
|
|
353
|
-
to_api_key = option_arg
|
|
354
|
-
if option_flag.name == "to_project_id":
|
|
355
|
-
to_project_id = option_arg
|
|
356
|
-
if option_flag.name == "to_instance":
|
|
357
|
-
to_instance = option_arg
|
|
358
|
-
if option_flag.name == "process_id":
|
|
359
|
-
process_id = option_arg
|
|
360
|
-
|
|
361
|
-
if not (from_project_id and from_instance and process_id):
|
|
362
|
-
raise MissingRequirementException("Cannot migrate resources without indicating source: project, instance, and process ID")
|
|
363
|
-
|
|
364
|
-
migration_strategy = AgenticProcessMigrationStrategy(
|
|
365
|
-
from_api_key=from_api_key,
|
|
366
|
-
from_project_id=from_project_id,
|
|
367
|
-
from_instance=from_instance,
|
|
368
|
-
to_api_key=to_api_key,
|
|
369
|
-
to_project_id=to_project_id,
|
|
370
|
-
to_instance=to_instance,
|
|
371
|
-
process_id=process_id
|
|
372
|
-
)
|
|
373
|
-
tool = MigrationTool(migration_strategy)
|
|
374
|
-
tool.run_migration()
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
clone_process_options = [
|
|
378
1023
|
Option(
|
|
379
|
-
"
|
|
380
|
-
["--
|
|
381
|
-
"
|
|
1024
|
+
"to_instance",
|
|
1025
|
+
["--to-instance", "--to-url"],
|
|
1026
|
+
"Destination instance URL (optional, defaults to source URL)",
|
|
382
1027
|
True
|
|
383
1028
|
),
|
|
384
1029
|
Option(
|
|
385
|
-
"
|
|
386
|
-
["--
|
|
387
|
-
"
|
|
1030
|
+
"to_project_name",
|
|
1031
|
+
["--to-project-name", "--to-name"],
|
|
1032
|
+
"Name for the new destination project (creates new project if specified with --admin-email)",
|
|
388
1033
|
True
|
|
389
1034
|
),
|
|
390
1035
|
Option(
|
|
391
|
-
"
|
|
392
|
-
["--
|
|
393
|
-
"
|
|
1036
|
+
"admin_email",
|
|
1037
|
+
["--admin-email"],
|
|
1038
|
+
"Admin email for new project (required when creating new project)",
|
|
394
1039
|
True
|
|
395
1040
|
),
|
|
396
1041
|
Option(
|
|
397
|
-
"
|
|
398
|
-
["--to-
|
|
399
|
-
"
|
|
1042
|
+
"to_project_id",
|
|
1043
|
+
["--to-project-id", "--to-pid"],
|
|
1044
|
+
"Destination project ID (optional for files)",
|
|
400
1045
|
True
|
|
401
1046
|
),
|
|
402
1047
|
Option(
|
|
403
|
-
"
|
|
404
|
-
["--to-
|
|
405
|
-
"ID
|
|
1048
|
+
"to_organization_id",
|
|
1049
|
+
["--to-organization-id", "--to-oid"],
|
|
1050
|
+
"Destination organization ID (optional for usage limits and files)",
|
|
406
1051
|
True
|
|
407
1052
|
),
|
|
408
1053
|
Option(
|
|
409
|
-
"
|
|
410
|
-
["--
|
|
411
|
-
"
|
|
412
|
-
|
|
1054
|
+
"all",
|
|
1055
|
+
["--all"],
|
|
1056
|
+
"Migrate all available components",
|
|
1057
|
+
False
|
|
413
1058
|
),
|
|
414
1059
|
Option(
|
|
415
|
-
"
|
|
416
|
-
["--
|
|
417
|
-
"
|
|
1060
|
+
"agents",
|
|
1061
|
+
["--agents"],
|
|
1062
|
+
"Agent IDs to migrate: comma-separated IDs or 'all'",
|
|
418
1063
|
True
|
|
419
1064
|
),
|
|
420
|
-
]
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
def clone_task(option_list: list):
|
|
424
|
-
from_api_key = None
|
|
425
|
-
from_project_id = None
|
|
426
|
-
from_instance = None
|
|
427
|
-
to_api_key = None
|
|
428
|
-
to_project_id = None
|
|
429
|
-
to_instance = None
|
|
430
|
-
task_id = None
|
|
431
|
-
|
|
432
|
-
for option_flag, option_arg in option_list:
|
|
433
|
-
if option_flag.name == "from_api_key":
|
|
434
|
-
from_api_key = option_arg
|
|
435
|
-
if option_flag.name == "from_project_id":
|
|
436
|
-
from_project_id = option_arg
|
|
437
|
-
if option_flag.name == "from_instance":
|
|
438
|
-
from_instance = option_arg
|
|
439
|
-
if option_flag.name == "to_api_key":
|
|
440
|
-
to_api_key = option_arg
|
|
441
|
-
if option_flag.name == "to_project_id":
|
|
442
|
-
to_project_id = option_arg
|
|
443
|
-
if option_flag.name == "to_instance":
|
|
444
|
-
to_instance = option_arg
|
|
445
|
-
if option_flag.name == "task_id":
|
|
446
|
-
task_id = option_arg
|
|
447
|
-
|
|
448
|
-
if not (from_project_id and from_instance and task_id):
|
|
449
|
-
raise MissingRequirementException("Cannot migrate resources without indicating source: project, instance, and task ID")
|
|
450
|
-
|
|
451
|
-
migration_strategy = TaskMigrationStrategy(
|
|
452
|
-
from_api_key=from_api_key,
|
|
453
|
-
from_project_id=from_project_id,
|
|
454
|
-
from_instance=from_instance,
|
|
455
|
-
to_api_key=to_api_key,
|
|
456
|
-
to_project_id=to_project_id,
|
|
457
|
-
to_instance=to_instance,
|
|
458
|
-
task_id=task_id
|
|
459
|
-
)
|
|
460
|
-
tool = MigrationTool(migration_strategy)
|
|
461
|
-
response = tool.run_migration()
|
|
462
|
-
|
|
463
|
-
Console.write_stdout(f"Migration result: \n{response}")
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
clone_task_options = [
|
|
467
1065
|
Option(
|
|
468
|
-
"
|
|
469
|
-
["--
|
|
470
|
-
"
|
|
1066
|
+
"tools",
|
|
1067
|
+
["--tools"],
|
|
1068
|
+
"Tool IDs to migrate: comma-separated IDs or 'all'",
|
|
471
1069
|
True
|
|
472
1070
|
),
|
|
473
1071
|
Option(
|
|
474
|
-
"
|
|
475
|
-
["--
|
|
476
|
-
"
|
|
1072
|
+
"agentic_processes",
|
|
1073
|
+
["--agentic-processes", "--processes"],
|
|
1074
|
+
"Agentic process IDs to migrate: comma-separated IDs or 'all'",
|
|
477
1075
|
True
|
|
478
1076
|
),
|
|
479
1077
|
Option(
|
|
480
|
-
"
|
|
481
|
-
["--
|
|
482
|
-
"
|
|
1078
|
+
"tasks",
|
|
1079
|
+
["--tasks"],
|
|
1080
|
+
"Task IDs to migrate: comma-separated IDs or 'all'",
|
|
483
1081
|
True
|
|
484
1082
|
),
|
|
485
1083
|
Option(
|
|
486
|
-
"
|
|
487
|
-
["--
|
|
488
|
-
"
|
|
489
|
-
|
|
1084
|
+
"usage_limits",
|
|
1085
|
+
["--usage-limits"],
|
|
1086
|
+
"Migrate usage limits (requires organization IDs)",
|
|
1087
|
+
False
|
|
490
1088
|
),
|
|
491
1089
|
Option(
|
|
492
|
-
"
|
|
493
|
-
["--
|
|
494
|
-
"
|
|
1090
|
+
"rag_assistants",
|
|
1091
|
+
["--rag-assistants"],
|
|
1092
|
+
"RAG assistant names to migrate: comma-separated names or 'all'",
|
|
495
1093
|
True
|
|
496
1094
|
),
|
|
497
1095
|
Option(
|
|
498
|
-
"
|
|
499
|
-
["--
|
|
500
|
-
"
|
|
1096
|
+
"files",
|
|
1097
|
+
["--files"],
|
|
1098
|
+
"File IDs to migrate: comma-separated IDs or 'all' (requires org/project IDs)",
|
|
501
1099
|
True
|
|
502
1100
|
),
|
|
503
1101
|
Option(
|
|
504
|
-
"
|
|
505
|
-
["--
|
|
506
|
-
"
|
|
1102
|
+
"stop_on_error",
|
|
1103
|
+
["--stop-on-error", "--soe"],
|
|
1104
|
+
"Stop migration on first error: 0: False, 1: True (default: 1)",
|
|
507
1105
|
True
|
|
508
1106
|
),
|
|
509
1107
|
]
|
|
510
1108
|
|
|
511
1109
|
|
|
512
|
-
|
|
513
1110
|
migrate_commands = [
|
|
514
1111
|
Command(
|
|
515
1112
|
"help",
|
|
@@ -523,47 +1120,10 @@ migrate_commands = [
|
|
|
523
1120
|
Command(
|
|
524
1121
|
"clone_project",
|
|
525
1122
|
["clone-project"],
|
|
526
|
-
"Clone project
|
|
1123
|
+
"Clone project components between instances",
|
|
527
1124
|
clone_project,
|
|
528
1125
|
ArgumentsEnum.REQUIRED,
|
|
529
1126
|
[],
|
|
530
1127
|
clone_project_options
|
|
531
1128
|
),
|
|
532
|
-
Command(
|
|
533
|
-
"clone_agent",
|
|
534
|
-
["clone-agent"],
|
|
535
|
-
"Clone agentt from instance",
|
|
536
|
-
clone_agent,
|
|
537
|
-
ArgumentsEnum.REQUIRED,
|
|
538
|
-
[],
|
|
539
|
-
clone_agent_options
|
|
540
|
-
),
|
|
541
|
-
Command(
|
|
542
|
-
"clone_tool",
|
|
543
|
-
["clone-tool"],
|
|
544
|
-
"Clone tool from instance",
|
|
545
|
-
clone_tool,
|
|
546
|
-
ArgumentsEnum.REQUIRED,
|
|
547
|
-
[],
|
|
548
|
-
clone_tool_options
|
|
549
|
-
),
|
|
550
|
-
Command(
|
|
551
|
-
"clone_process",
|
|
552
|
-
["clone-process"],
|
|
553
|
-
"Clone process from instance",
|
|
554
|
-
clone_process,
|
|
555
|
-
ArgumentsEnum.REQUIRED,
|
|
556
|
-
[],
|
|
557
|
-
clone_process_options
|
|
558
|
-
),
|
|
559
|
-
# TODO -> Remove clone-task: clone-process includes cloning tasks
|
|
560
|
-
# Command(
|
|
561
|
-
# "clone_task",
|
|
562
|
-
# ["clone-task"],
|
|
563
|
-
# "Clone task from instance",
|
|
564
|
-
# clone_task,
|
|
565
|
-
# ArgumentsEnum.REQUIRED,
|
|
566
|
-
# [],
|
|
567
|
-
# clone_task_options
|
|
568
|
-
#),
|
|
569
1129
|
]
|