ibm-watsonx-orchestrate 1.0.0__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.
Files changed (89) hide show
  1. ibm_watsonx_orchestrate/__init__.py +28 -0
  2. ibm_watsonx_orchestrate/agent_builder/__init__.py +0 -0
  3. ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +5 -0
  4. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +27 -0
  5. ibm_watsonx_orchestrate/agent_builder/agents/assistant_agent.py +28 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/external_agent.py +28 -0
  7. ibm_watsonx_orchestrate/agent_builder/agents/types.py +204 -0
  8. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +27 -0
  9. ibm_watsonx_orchestrate/agent_builder/connections/connections.py +123 -0
  10. ibm_watsonx_orchestrate/agent_builder/connections/types.py +260 -0
  11. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base.py +27 -0
  12. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/knowledge_base_requests.py +59 -0
  13. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +243 -0
  14. ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +4 -0
  15. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +36 -0
  16. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +332 -0
  17. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +195 -0
  18. ibm_watsonx_orchestrate/agent_builder/tools/types.py +162 -0
  19. ibm_watsonx_orchestrate/agent_builder/utils/__init__.py +0 -0
  20. ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py +149 -0
  21. ibm_watsonx_orchestrate/cli/__init__.py +0 -0
  22. ibm_watsonx_orchestrate/cli/commands/__init__.py +0 -0
  23. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +192 -0
  24. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +660 -0
  25. ibm_watsonx_orchestrate/cli/commands/channels/channels_command.py +15 -0
  26. ibm_watsonx_orchestrate/cli/commands/channels/channels_controller.py +16 -0
  27. ibm_watsonx_orchestrate/cli/commands/channels/types.py +15 -0
  28. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_command.py +32 -0
  29. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +141 -0
  30. ibm_watsonx_orchestrate/cli/commands/chat/chat_command.py +43 -0
  31. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +307 -0
  32. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +517 -0
  33. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +78 -0
  34. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +189 -0
  35. ibm_watsonx_orchestrate/cli/commands/environment/types.py +9 -0
  36. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +79 -0
  37. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +201 -0
  38. ibm_watsonx_orchestrate/cli/commands/login/login_command.py +17 -0
  39. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +128 -0
  40. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +623 -0
  41. ibm_watsonx_orchestrate/cli/commands/settings/__init__.py +0 -0
  42. ibm_watsonx_orchestrate/cli/commands/settings/observability/__init__.py +0 -0
  43. ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/__init__.py +0 -0
  44. ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/langfuse_command.py +175 -0
  45. ibm_watsonx_orchestrate/cli/commands/settings/observability/observability_command.py +11 -0
  46. ibm_watsonx_orchestrate/cli/commands/settings/settings_command.py +10 -0
  47. ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +85 -0
  48. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +564 -0
  49. ibm_watsonx_orchestrate/cli/commands/tools/types.py +10 -0
  50. ibm_watsonx_orchestrate/cli/config.py +226 -0
  51. ibm_watsonx_orchestrate/cli/main.py +32 -0
  52. ibm_watsonx_orchestrate/client/__init__.py +0 -0
  53. ibm_watsonx_orchestrate/client/agents/agent_client.py +46 -0
  54. ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +38 -0
  55. ibm_watsonx_orchestrate/client/agents/external_agent_client.py +38 -0
  56. ibm_watsonx_orchestrate/client/analytics/__init__.py +0 -0
  57. ibm_watsonx_orchestrate/client/analytics/llm/__init__.py +0 -0
  58. ibm_watsonx_orchestrate/client/analytics/llm/analytics_llm_client.py +50 -0
  59. ibm_watsonx_orchestrate/client/base_api_client.py +113 -0
  60. ibm_watsonx_orchestrate/client/base_service_instance.py +10 -0
  61. ibm_watsonx_orchestrate/client/client.py +71 -0
  62. ibm_watsonx_orchestrate/client/client_errors.py +359 -0
  63. ibm_watsonx_orchestrate/client/connections/__init__.py +10 -0
  64. ibm_watsonx_orchestrate/client/connections/connections_client.py +162 -0
  65. ibm_watsonx_orchestrate/client/connections/utils.py +27 -0
  66. ibm_watsonx_orchestrate/client/credentials.py +123 -0
  67. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +46 -0
  68. ibm_watsonx_orchestrate/client/local_service_instance.py +91 -0
  69. ibm_watsonx_orchestrate/client/service_instance.py +73 -0
  70. ibm_watsonx_orchestrate/client/tools/tool_client.py +41 -0
  71. ibm_watsonx_orchestrate/client/utils.py +95 -0
  72. ibm_watsonx_orchestrate/docker/compose-lite.yml +595 -0
  73. ibm_watsonx_orchestrate/docker/default.env +125 -0
  74. ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl +0 -0
  75. ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0.tar.gz +0 -0
  76. ibm_watsonx_orchestrate/docker/start-up.sh +61 -0
  77. ibm_watsonx_orchestrate/docker/tempus/common-config.yaml +1 -0
  78. ibm_watsonx_orchestrate/run/__init__.py +0 -0
  79. ibm_watsonx_orchestrate/run/connections.py +40 -0
  80. ibm_watsonx_orchestrate/utils/__init__.py +0 -0
  81. ibm_watsonx_orchestrate/utils/logging/__init__.py +0 -0
  82. ibm_watsonx_orchestrate/utils/logging/logger.py +26 -0
  83. ibm_watsonx_orchestrate/utils/logging/logging.yaml +18 -0
  84. ibm_watsonx_orchestrate/utils/utils.py +15 -0
  85. ibm_watsonx_orchestrate-1.0.0.dist-info/METADATA +34 -0
  86. ibm_watsonx_orchestrate-1.0.0.dist-info/RECORD +89 -0
  87. ibm_watsonx_orchestrate-1.0.0.dist-info/WHEEL +4 -0
  88. ibm_watsonx_orchestrate-1.0.0.dist-info/entry_points.txt +2 -0
  89. ibm_watsonx_orchestrate-1.0.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,517 @@
1
+ import logging
2
+ import requests
3
+ import json
4
+ import rich
5
+ import yaml
6
+ import sys
7
+ import typer
8
+
9
+ from typing import List
10
+ from ibm_watsonx_orchestrate.client.utils import is_local_dev
11
+ from ibm_watsonx_orchestrate.agent_builder.connections.types import (
12
+ ConnectionEnvironment,
13
+ ConnectionConfiguration,
14
+ ConnectionSecurityScheme,
15
+ ConnectionType,
16
+ IdpConfigData,
17
+ IdpConfigDataBody,
18
+ AppConfigData,
19
+ BasicAuthCredentials,
20
+ BearerTokenAuthCredentials,
21
+ APIKeyAuthCredentials,
22
+ # OAuth2AuthCodeCredentials,
23
+ # OAuth2ClientCredentials,
24
+ # OAuth2ImplicitCredentials,
25
+ # OAuth2PasswordCredentials,
26
+ OAuthOnBehalfOfCredentials,
27
+ KeyValueConnectionCredentials,
28
+ CREDENTIALS,
29
+ IdentityProviderCredentials,
30
+ OAUTH_CONNECTION_TYPES
31
+
32
+ )
33
+
34
+ from ibm_watsonx_orchestrate.client.connections import get_connections_client, get_connection_type
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ def _validate_connections_spec_content(content: dict) -> None:
39
+ spec_version = content.get("spec_version")
40
+ kind = content.get("kind")
41
+ app_id = content.get("app_id")
42
+ environments = content.get("environments")
43
+
44
+ if not spec_version:
45
+ logger.error("No 'spec_version' found in provided spec file. Please ensure the spec file is in the correct format")
46
+ sys.exit(1)
47
+ if not kind:
48
+ logger.error("No 'kind' found in provided spec file. Please ensure the spec file is in the correct format")
49
+ sys.exit(1)
50
+ if not app_id:
51
+ logger.error("No 'app_id' found in provided spec file. Please ensure the spec file is in the correct format")
52
+ sys.exit(1)
53
+ if not environments or not len(environments):
54
+ logger.error("No 'environments' found in provided spec file. Please ensure the spec file is in the correct format")
55
+ sys.exit(1)
56
+
57
+ if kind != "connection":
58
+ logger.error("Field 'kind' must have a value of 'connection'. Please ensure the spec file is a valid connection spec.")
59
+ sys.exit(1)
60
+
61
+ def _create_connection_from_spec(content: dict) -> None:
62
+ if not content:
63
+ logger.error("No spec content provided. Please verify the input file is not empty")
64
+ sys.exit(1)
65
+
66
+ client = get_connections_client()
67
+
68
+ _validate_connections_spec_content(content=content)
69
+
70
+ app_id = content.get("app_id")
71
+ existing_app = client.get(app_id=app_id)
72
+ if not existing_app:
73
+ add_connection(app_id=app_id)
74
+
75
+ environments = content.get("environments")
76
+ for environment in environments:
77
+ if is_local_dev() and environment != ConnectionEnvironment.DRAFT:
78
+ logger.warning(f"Local development does not support any environments other than 'draft'. The provided '{environment}' environment configuration will be ignored.")
79
+ continue
80
+ config = environments.get(environment)
81
+ config["environment"] = environment
82
+ config["app_id"] = app_id
83
+ config["environment"] = environment
84
+ config = ConnectionConfiguration.model_validate(config)
85
+ add_configuration(config)
86
+
87
+ def _parse_file(file: str) -> None:
88
+ if file.endswith('.yaml') or file.endswith('.yml') or file.endswith(".json"):
89
+ with open(file, 'r') as f:
90
+ if file.endswith(".json"):
91
+ content = json.load(f)
92
+ else:
93
+ content = yaml.load(f, Loader=yaml.SafeLoader)
94
+ _create_connection_from_spec(content=content)
95
+ else:
96
+ raise ValueError("file must end in .json, .yaml or .yml")
97
+
98
+ def _format_token_headers(header_list: List) -> dict:
99
+ if not header_list or len(header_list) == 0:
100
+ return None
101
+
102
+ header = dict()
103
+
104
+ for header_string in header_list:
105
+ split_header = header_string.split(":")
106
+ if len(split_header) != 2:
107
+ logger.error(f"Provided header '{header_string}' is not in the correct format. Please format headers as 'key: value'")
108
+ sys.exit(1)
109
+ header_key, header_value = split_header
110
+ header_key = header_key.strip()
111
+ header_value = header_value.strip()
112
+
113
+ header[header_key] = header_value
114
+
115
+ return header
116
+
117
+ def _validate_connection_params(type: ConnectionType, **args) -> None:
118
+
119
+ if type == ConnectionType.BASIC_AUTH and (
120
+ args.get('username') is None or args.get('password') is None
121
+ ):
122
+ raise typer.BadParameter(
123
+ f"Missing flags --username (-u) and --password (-p) are both required for type {type}"
124
+ )
125
+
126
+ if type == ConnectionType.BEARER_TOKEN and (
127
+ args.get('token') is None
128
+ ):
129
+ raise typer.BadParameter(
130
+ f"Missing flags --token is required for type {type}"
131
+ )
132
+
133
+ if type == ConnectionType.API_KEY_AUTH and (
134
+ args.get('api_key') is None
135
+ ):
136
+ raise typer.BadParameter(
137
+ f"Missing flags --api-key is required for type {type}"
138
+ )
139
+
140
+ # if type in OAUTH_CONNECTION_TYPES and args.get('client_id') is None:
141
+ # raise typer.BadParameter(
142
+ # f"Missing flags --client-id is required for type {type}"
143
+ # )
144
+
145
+ # if type in (OAUTH_CONNECTION_TYPES.difference({ConnectionType.OAUTH2_IMPLICIT, ConnectionType.OAUTH_ON_BEHALF_OF_FLOW})) and args.get('client_secret') is None:
146
+ # raise typer.BadParameter(
147
+ # f"Missing flags --client-secret is required for type {type}"
148
+ # )
149
+
150
+ # if type in (OAUTH_CONNECTION_TYPES.difference({ConnectionType.OAUTH2_IMPLICIT})) and args.get('token_url') is None:
151
+ # raise typer.BadParameter(
152
+ # f"Missing flags --token-url is required for type {type}"
153
+ # )
154
+
155
+ # if type in (OAUTH_CONNECTION_TYPES.difference({ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH_ON_BEHALF_OF_FLOW})) and args.get('auth_url') is None:
156
+ # raise typer.BadParameter(
157
+ # f"Missing flags --auth-url is required for type {type}"
158
+ # )
159
+
160
+ if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
161
+ args.get('client_id') is None
162
+ ):
163
+ raise typer.BadParameter(
164
+ f"Missing flags --client-id is required for type {type}"
165
+ )
166
+
167
+ if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
168
+ args.get('token_url') is None
169
+ ):
170
+ raise typer.BadParameter(
171
+ f"Missing flags --token-url is required for type {type}"
172
+ )
173
+
174
+ if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
175
+ args.get('grant_type') is None
176
+ ):
177
+ raise typer.BadParameter(
178
+ f"Missing flags --grant-type is required for type {type}"
179
+ )
180
+
181
+ def _parse_entry(entry: str) -> dict[str,str]:
182
+ split_entry = entry.split('=')
183
+ if len(split_entry) != 2:
184
+ message = f"The entry '{entry}' is not in the expected form '<key>=<value>'"
185
+ logger.error(message)
186
+ exit(1)
187
+ return {split_entry[0]: split_entry[1]}
188
+
189
+ def _get_credentials(type: ConnectionType, **kwargs):
190
+ match type:
191
+ case ConnectionType.BASIC_AUTH:
192
+ return BasicAuthCredentials(
193
+ username=kwargs.get("username"),
194
+ password=kwargs.get("password")
195
+ )
196
+ case ConnectionType.BEARER_TOKEN:
197
+ return BearerTokenAuthCredentials(
198
+ token=kwargs.get("token")
199
+ )
200
+ case ConnectionType.API_KEY_AUTH:
201
+ return APIKeyAuthCredentials(
202
+ api_key=kwargs.get("api_key")
203
+ )
204
+ # case ConnectionType.OAUTH2_AUTH_CODE:
205
+ # return OAuth2AuthCodeCredentials(
206
+ # authorization_url=kwargs.get("auth_url"),
207
+ # client_id=kwargs.get("client_id"),
208
+ # client_secret=kwargs.get("client_secret"),
209
+ # token_url=kwargs.get("token_url")
210
+ # )
211
+ # case ConnectionType.OAUTH2_CLIENT_CREDS:
212
+ # return OAuth2ClientCredentials(
213
+ # client_id=kwargs.get("client_id"),
214
+ # client_secret=kwargs.get("client_secret"),
215
+ # token_url=kwargs.get("token_url")
216
+ # )
217
+ # case ConnectionType.OAUTH2_IMPLICIT:
218
+ # return OAuth2ImplicitCredentials(
219
+ # authorization_url=kwargs.get("auth_url"),
220
+ # client_id=kwargs.get("client_id"),
221
+ # )
222
+ # case ConnectionType.OAUTH2_PASSWORD:
223
+ # return OAuth2PasswordCredentials(
224
+ # authorization_url=kwargs.get("auth_url"),
225
+ # client_id=kwargs.get("client_id"),
226
+ # client_secret=kwargs.get("client_secret"),
227
+ # token_url=kwargs.get("token_url")
228
+ # )
229
+ case ConnectionType.OAUTH_ON_BEHALF_OF_FLOW:
230
+ return OAuthOnBehalfOfCredentials(
231
+ client_id=kwargs.get("client_id"),
232
+ access_token_url=kwargs.get("token_url"),
233
+ grant_type=kwargs.get("grant_type")
234
+ )
235
+ case ConnectionType.KEY_VALUE:
236
+ env = {}
237
+ for entry in kwargs.get('entries', []):
238
+ env.update(_parse_entry(entry))
239
+
240
+ return KeyValueConnectionCredentials(
241
+ env
242
+ )
243
+ case _:
244
+ raise ValueError(f"Invalid type '{type}' selected")
245
+
246
+
247
+ def add_configuration(config: ConnectionConfiguration) -> None:
248
+ client = get_connections_client()
249
+ app_id = config.app_id
250
+ environment = config.environment
251
+
252
+ try:
253
+ existing_configuration = client.get_config(app_id=app_id, env=environment)
254
+ if existing_configuration:
255
+ logger.info(f"Existing connection '{app_id}' with environment '{environment}' found. Updating configuration")
256
+ should_delete_credentials = False
257
+
258
+ if existing_configuration.security_scheme != config.security_scheme:
259
+ should_delete_credentials = True
260
+ logger.warning(f"Detected a change in auth type from '{existing_configuration.security_scheme}' to '{config.security_scheme}'. The associated credentials will be removed.")
261
+ elif existing_configuration.auth_type != config.auth_type:
262
+ should_delete_credentials = True
263
+ logger.warning(f"Detected a change in oauth flow from '{existing_configuration.auth_type}' to '{config.auth_type}'. The associated credentials will be removed.")
264
+ elif existing_configuration.preference != config.preference:
265
+ should_delete_credentials = True
266
+ logger.warning(f"Detected a change in preference/type from '{existing_configuration.preference}' to '{config.preference}'. The associated credentials will be removed.")
267
+ elif existing_configuration.sso != config.sso:
268
+ logger.warning(f"Detected a change in sso from '{existing_configuration.sso}' to '{config.sso}'. The associated credentials will be removed.")
269
+ should_delete_credentials = True
270
+
271
+ if should_delete_credentials:
272
+ try:
273
+ existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=existing_configuration.sso)
274
+ if existing_credentials:
275
+ client.delete_credentials(app_id=app_id, env=environment, use_sso=existing_configuration.sso)
276
+ except:
277
+ logger.error(f"Error removing credentials for connection '{app_id}' in environment '{environment}'. No changes have been made to the configuration.")
278
+ sys.exit(1)
279
+
280
+ client.update_config(app_id=app_id, env=environment, payload=config.model_dump(exclude_none=True))
281
+ logger.info(f"Configuration successfully updated for '{environment}' environment of connection '{app_id}'.")
282
+ else:
283
+ logger.info(f"Creating configuration for connection '{app_id}' in the '{environment}' environment")
284
+ client.create_config(app_id=app_id, payload=config.model_dump())
285
+ logger.info(f"Configuration successfully created for '{environment}' environment of connection '{app_id}'.")
286
+
287
+ except requests.HTTPError as e:
288
+ response = e.response
289
+ response_text = response.text
290
+ logger.error(response_text)
291
+ exit(1)
292
+
293
+ def add_credentials(app_id: str, environment: ConnectionEnvironment, use_sso: bool, credentials: CREDENTIALS) -> None:
294
+ client = get_connections_client()
295
+ try:
296
+ existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=use_sso)
297
+ if use_sso:
298
+ payload = {
299
+ "app_credentials": credentials.model_dump(exclude_none=True)
300
+ }
301
+ else:
302
+ payload = {
303
+ "runtime_credentials": credentials.model_dump(exclude_none=True)
304
+ }
305
+
306
+ logger.info(f"Setting credentials for environment '{environment}' on connection '{app_id}'")
307
+ if existing_credentials:
308
+ client.update_credentials(app_id=app_id, env=environment, use_sso=use_sso, payload=payload)
309
+ else:
310
+ client.create_credentials(app_id=app_id,env=environment, use_sso=use_sso, payload=payload)
311
+ logger.info(f"Credentials successfully set for '{environment}' environment of connection '{app_id}'")
312
+ except requests.HTTPError as e:
313
+ response = e.response
314
+ response_text = response.text
315
+ logger.error(response_text)
316
+ exit(1)
317
+
318
+ def add_identity_provider(app_id: str, environment: ConnectionEnvironment, idp: IdentityProviderCredentials):
319
+ client = get_connections_client()
320
+
321
+ try:
322
+ existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=True)
323
+
324
+ payload = {
325
+ "idp_credentials": idp.model_dump()
326
+ }
327
+
328
+ logger.info(f"Setting identity provider for environment '{environment}' on connection '{app_id}'")
329
+ if existing_credentials:
330
+ client.update_credentials(app_id=app_id, env=environment, use_sso=True, payload=payload)
331
+ else:
332
+ client.create_credentials(app_id=app_id,env=environment, use_sso=True, payload=payload)
333
+ logger.info(f"Identity provider successfully set for '{environment}' environment of connection '{app_id}'")
334
+ except requests.HTTPError as e:
335
+ response = e.response
336
+ response_text = response.text
337
+ logger.error(response_text)
338
+ exit(1)
339
+
340
+ def add_connection(app_id: str) -> None:
341
+ client = get_connections_client()
342
+
343
+ try:
344
+ logger.info(f"Creating connection '{app_id}'")
345
+ request = {"app_id": app_id}
346
+ client.create(payload=request)
347
+ logger.info(f"Successfully created connection '{app_id}'")
348
+ except requests.HTTPError as e:
349
+ response = e.response
350
+ response_text = response.text
351
+ status_code = response.status_code
352
+ try:
353
+ if status_code == 409:
354
+ response_text = f"Failed to create connection. A connection with the App ID '{app_id}' already exists. Please select a diffrent App ID or delete the existing resource."
355
+ else:
356
+ resp = json.loads(response_text)
357
+ response_text = resp.get('detail')
358
+ except:
359
+ pass
360
+ logger.error(response_text)
361
+ exit(1)
362
+
363
+ def remove_connection(app_id: str) -> None:
364
+ client = get_connections_client()
365
+
366
+ try:
367
+ logger.info(f"Removing connection '{app_id}'")
368
+ client.delete(app_id=app_id)
369
+ logger.info(f"Connection '{app_id}' successfully removed")
370
+ except requests.HTTPError as e:
371
+ response = e.response
372
+ response_text = response.text
373
+ logger.error(response_text)
374
+ exit(1)
375
+
376
+ def list_connections(environment: ConnectionEnvironment | None, verbose: bool = False) -> None:
377
+ client = get_connections_client()
378
+
379
+ connections = client.list()
380
+
381
+ if verbose:
382
+ connections_list = []
383
+ for conn in connections:
384
+ connections_list.append(json.loads(conn.model_dump_json()))
385
+
386
+ rich.print_json(json.dumps(connections_list, indent=4))
387
+ else:
388
+ non_configured_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="*Non-Configured")
389
+ draft_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="Draft")
390
+ live_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="Live")
391
+ columns = ["App ID", "Auth Type", "Type", "Credentials Set"]
392
+ for column in columns:
393
+ draft_table.add_column(column, justify='center', no_wrap=True)
394
+ live_table.add_column(column, justify='center', no_wrap=True)
395
+ non_configured_table.add_column(column, justify='center', no_wrap=True)
396
+
397
+ for conn in connections:
398
+ if conn.environment is None:
399
+ non_configured_table.add_row(
400
+ conn.app_id,
401
+ "n/a",
402
+ "n/a",
403
+ "❌"
404
+ )
405
+ continue
406
+
407
+ connection_type = get_connection_type(security_scheme=conn.security_scheme, auth_type=conn.auth_type)
408
+
409
+ if conn.environment == ConnectionEnvironment.DRAFT:
410
+ draft_table.add_row(
411
+ conn.app_id,
412
+ connection_type,
413
+ conn.preference,
414
+ "✅" if conn.credentials_entered else "❌"
415
+ )
416
+ elif conn.environment == ConnectionEnvironment.LIVE:
417
+ live_table.add_row(
418
+ conn.app_id,
419
+ connection_type,
420
+ conn.preference,
421
+ "✅" if conn.credentials_entered else "❌"
422
+ )
423
+ if environment is None and len(non_configured_table.rows):
424
+ rich.print(non_configured_table)
425
+ if environment == ConnectionEnvironment.DRAFT or (environment == None and len(draft_table.rows)):
426
+ rich.print(draft_table)
427
+ if environment == ConnectionEnvironment.LIVE or (environment == None and len(live_table.rows)):
428
+ rich.print(live_table)
429
+ if environment == None and not len(draft_table.rows) and not len(live_table.rows) and not len(non_configured_table.rows):
430
+ logger.info("No connections found. You can create connections using `orchestrate connections add`")
431
+
432
+ def import_connection(file: str) -> None:
433
+ _parse_file(file=file)
434
+
435
+ def configure_connection(**kwargs) -> None:
436
+ if is_local_dev() and kwargs.get("environment") != ConnectionEnvironment.DRAFT:
437
+ logger.error(f"Cannot create configuration for environment '{kwargs.get('environment')}'. Local development does not support any environments other than 'draft'.")
438
+ sys.exit(1)
439
+
440
+ idp_config_body = None
441
+ if kwargs.get("idp_token_type") or kwargs.get("idp_token_use"):
442
+ idp_config_body = IdpConfigDataBody(
443
+ requested_token_type=kwargs.get("idp_token_type"),
444
+ requested_token_use=kwargs.get("idp_token_use")
445
+ )
446
+
447
+
448
+ idp_config_data = None
449
+ if idp_config_body or kwargs.get("idp_token_header"):
450
+ idp_config_data = IdpConfigData(
451
+ header=_format_token_headers(kwargs.get("idp_token_header")),
452
+ body=idp_config_body
453
+ )
454
+
455
+ app_config_data = AppConfigData() if kwargs.get("sso", False) else None
456
+ if kwargs.get("app_token_header"):
457
+ app_config_data = AppConfigData(
458
+ header=_format_token_headers(kwargs.get("app_token_header"))
459
+ )
460
+
461
+ kwargs["idp_config_data"] = idp_config_data
462
+ kwargs["app_config_data"] = app_config_data
463
+
464
+ config = ConnectionConfiguration.model_validate(kwargs)
465
+
466
+ # TODO: Remove once Oauth is supported on local
467
+ if config.security_scheme == ConnectionSecurityScheme.OAUTH2 and is_local_dev():
468
+ logger.error("Use of OAuth connections unsupported for local development at this time.")
469
+ sys.exit(1)
470
+
471
+ add_configuration(config)
472
+
473
+ def set_credentials_connection(
474
+ app_id: str,
475
+ environment: ConnectionEnvironment,
476
+ **kwargs
477
+ ) -> None:
478
+ client = get_connections_client()
479
+
480
+ config = client.get_config(app_id=app_id, env=environment)
481
+ if not config:
482
+ logger.error(f"No configuration '{environment}' found for connection '{app_id}'. Please create the connection using `orchestrate connections add --app-id {app_id}` then add a configuration `orchestrate connections configure --app-id {app_id} --environment {environment} ...`")
483
+ sys.exit(1)
484
+
485
+ sso_enabled = config.sso
486
+ conn_type = get_connection_type(security_scheme=config.security_scheme, auth_type=config.auth_type)
487
+
488
+ _validate_connection_params(type=conn_type, **kwargs)
489
+ credentials = _get_credentials(type=conn_type, **kwargs)
490
+
491
+ add_credentials(app_id=app_id, environment=environment, use_sso=sso_enabled, credentials=credentials)
492
+
493
+ def set_identity_provider_connection(
494
+ app_id: str,
495
+ environment: ConnectionEnvironment,
496
+ **kwargs
497
+ ) -> None:
498
+ client = get_connections_client()
499
+
500
+ config = client.get_config(app_id=app_id, env=environment)
501
+ if not config:
502
+ logger.error(f"No configuration '{environment}' found for connection '{app_id}'. Please create the connection using `orchestrate connections add --app-id {app_id}` then add a configuration `orchestrate connections configure --app-id {app_id} --environment {environment} ...`")
503
+ sys.exit(1)
504
+
505
+ sso_enabled = config.sso
506
+ security_scheme = config.security_scheme
507
+
508
+ if security_scheme != ConnectionSecurityScheme.OAUTH2:
509
+ logger.error(f"Identity providers cannot be set for non-OAuth connection types. The connections specified is of type '{security_scheme}'")
510
+ sys.exit(1)
511
+
512
+ if not sso_enabled:
513
+ logger.error(f"Cannot set Identity Provider when 'sso' is false in configuration. Please enable sso for connection '{app_id}' in environment '{environment}' and try again.")
514
+ sys.exit(1)
515
+
516
+ idp = IdentityProviderCredentials.model_validate(kwargs)
517
+ add_identity_provider(app_id=app_id, environment=environment, idp=idp)
@@ -0,0 +1,78 @@
1
+ import logging
2
+ import typer
3
+ from typing_extensions import Annotated
4
+ from ibm_watsonx_orchestrate.cli.commands.environment import environment_controller
5
+ from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
6
+ from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
7
+ from ibm_watsonx_orchestrate.client.utils import is_local_dev
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ environment_app = typer.Typer(no_args_is_help=True)
12
+
13
+
14
+ @environment_app.command(name="activate")
15
+ def activate_env(
16
+ name: Annotated[
17
+ str,
18
+ typer.Argument(),
19
+ ],
20
+ apikey: Annotated[
21
+ str,
22
+ typer.Option(
23
+ "--apikey", "-a", help="WXO API Key. Leave Blank if developing locally"
24
+ ),
25
+ ] = None,
26
+ registry: Annotated[
27
+ RegistryType,
28
+ typer.Option("--registry", help="Which registry to use when importing python tools", hidden=True),
29
+ ] = None,
30
+ test_package_version_override: Annotated[
31
+ str,
32
+ typer.Option("--test-package-version-override", help="Which prereleased package version to reference when using --registry testpypi", hidden=True),
33
+ ] = None
34
+ ):
35
+ environment_controller.activate(name=name, apikey=apikey, registry=registry, test_package_version_override=test_package_version_override)
36
+
37
+
38
+ @environment_app.command(name="add")
39
+ def add_env(
40
+ name: Annotated[
41
+ str,
42
+ typer.Option("--name", "-n", help="Name of the environment you wish to create"),
43
+ ],
44
+ url: Annotated[
45
+ str,
46
+ typer.Option("--url", "-u", help="URL for the watsonX Orchestrate instance"),
47
+ ],
48
+ activate: Annotated[
49
+ bool,
50
+ typer.Option("--activate", "-a", help="Activate the newly created environment"),
51
+ ] = False,
52
+ iam_url: Annotated[
53
+ str,
54
+ typer.Option(
55
+ "--iam-url", "-i", help="The URL for the IAM token authentication", hidden=True
56
+ ),
57
+ ] = None,
58
+ type: Annotated[
59
+ EnvironmentAuthType,
60
+ typer.Option("--type", "-t", help="The type of auth you wish to use"),
61
+ ] = None
62
+ ):
63
+ environment_controller.add(name=name, url=url, should_activate=activate, iam_url=iam_url, type=type)
64
+
65
+
66
+ @environment_app.command(name="remove")
67
+ def remove_env(
68
+ name: Annotated[
69
+ str,
70
+ typer.Option("--name", "-n", help="Name of the environment you wish to create"),
71
+ ],
72
+ ):
73
+ environment_controller.remove(name=name)
74
+
75
+
76
+ @environment_app.command(name="list")
77
+ def list_envs():
78
+ environment_controller.list_envs()