ibm-watsonx-orchestrate 1.5.1__py3-none-any.whl → 1.7.0a0__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 (70) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/__init__.py +1 -1
  3. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
  4. ibm_watsonx_orchestrate/agent_builder/agents/types.py +58 -4
  5. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +33 -0
  7. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
  8. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
  9. ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
  10. ibm_watsonx_orchestrate/agent_builder/model_policies/types.py +1 -1
  11. ibm_watsonx_orchestrate/agent_builder/models/types.py +0 -1
  12. ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
  13. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +41 -3
  14. ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py +2 -1
  15. ibm_watsonx_orchestrate/agent_builder/tools/types.py +14 -1
  16. ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py +18 -1
  17. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +122 -17
  18. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -22
  19. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
  20. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -58
  21. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +29 -4
  22. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +74 -8
  23. ibm_watsonx_orchestrate/cli/commands/environment/types.py +1 -0
  24. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +224 -0
  25. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +158 -0
  26. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py +2 -2
  27. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
  28. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +31 -25
  29. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +6 -6
  30. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +17 -8
  31. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +162 -27
  32. ibm_watsonx_orchestrate/cli/commands/server/types.py +2 -1
  33. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -6
  34. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +37 -22
  35. ibm_watsonx_orchestrate/cli/config.py +2 -0
  36. ibm_watsonx_orchestrate/cli/main.py +6 -0
  37. ibm_watsonx_orchestrate/client/agents/agent_client.py +83 -9
  38. ibm_watsonx_orchestrate/client/agents/assistant_agent_client.py +3 -3
  39. ibm_watsonx_orchestrate/client/agents/external_agent_client.py +2 -2
  40. ibm_watsonx_orchestrate/client/base_api_client.py +11 -10
  41. ibm_watsonx_orchestrate/client/connections/connections_client.py +49 -14
  42. ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
  43. ibm_watsonx_orchestrate/client/credentials.py +4 -0
  44. ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
  45. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +2 -2
  46. ibm_watsonx_orchestrate/client/service_instance.py +42 -1
  47. ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
  48. ibm_watsonx_orchestrate/client/utils.py +37 -2
  49. ibm_watsonx_orchestrate/docker/compose-lite.yml +425 -81
  50. ibm_watsonx_orchestrate/docker/default.env +53 -15
  51. ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
  52. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/__init__.py +3 -2
  53. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +77 -0
  54. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/events.py +6 -1
  55. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/flow.py +85 -92
  56. ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/types.py +15 -6
  57. ibm_watsonx_orchestrate/flow_builder/utils.py +215 -0
  58. ibm_watsonx_orchestrate/run/connections.py +4 -4
  59. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/METADATA +2 -1
  60. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/RECORD +68 -61
  61. ibm_watsonx_orchestrate/experimental/flow_builder/flows/decorators.py +0 -144
  62. ibm_watsonx_orchestrate/experimental/flow_builder/utils.py +0 -115
  63. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/__init__.py +0 -0
  64. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/data_map.py +0 -0
  65. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/flows/constants.py +0 -0
  66. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/node.py +0 -0
  67. /ibm_watsonx_orchestrate/{experimental/flow_builder → flow_builder}/resources/flow_status.openapi.yml +0 -0
  68. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/WHEEL +0 -0
  69. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/entry_points.txt +0 -0
  70. {ibm_watsonx_orchestrate-1.5.1.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/licenses/LICENSE +0 -0
@@ -15,19 +15,19 @@ from ibm_watsonx_orchestrate.agent_builder.connections.types import (
15
15
  ConnectionType,
16
16
  IdpConfigData,
17
17
  IdpConfigDataBody,
18
- AppConfigData,
18
+ AppConfigData,
19
19
  BasicAuthCredentials,
20
20
  BearerTokenAuthCredentials,
21
21
  APIKeyAuthCredentials,
22
22
  # OAuth2AuthCodeCredentials,
23
- # OAuth2ClientCredentials,
23
+ OAuth2ClientCredentials,
24
24
  # OAuth2ImplicitCredentials,
25
25
  # OAuth2PasswordCredentials,
26
26
  OAuthOnBehalfOfCredentials,
27
27
  KeyValueConnectionCredentials,
28
28
  CREDENTIALS,
29
29
  IdentityProviderCredentials,
30
- OAUTH_CONNECTION_TYPES
30
+ OAUTH_CONNECTION_TYPES, OAuth2AuthCodeCredentials
31
31
 
32
32
  )
33
33
 
@@ -80,7 +80,6 @@ def _create_connection_from_spec(content: dict) -> None:
80
80
  config = environments.get(environment)
81
81
  config["environment"] = environment
82
82
  config["app_id"] = app_id
83
- config["environment"] = environment
84
83
  config = ConnectionConfiguration.model_validate(config)
85
84
  add_configuration(config)
86
85
 
@@ -137,40 +136,31 @@ def _validate_connection_params(type: ConnectionType, **args) -> None:
137
136
  f"Missing flags --api-key is required for type {type}"
138
137
  )
139
138
 
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
- # )
139
+ if type in {ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and args.get('client_secret') is None:
140
+ raise typer.BadParameter(
141
+ f"Missing flags --client-secret is required for type {type}"
142
+ )
154
143
 
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
- # )
144
+ if type in {ConnectionType.OAUTH2_AUTH_CODE} and args.get('auth_url') is None:
145
+ raise typer.BadParameter(
146
+ f"Missing flags --auth-url is required for type {type}"
147
+ )
159
148
 
160
- if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
149
+ if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and (
161
150
  args.get('client_id') is None
162
151
  ):
163
152
  raise typer.BadParameter(
164
153
  f"Missing flags --client-id is required for type {type}"
165
154
  )
166
155
 
167
- if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
156
+ if type in {ConnectionType.OAUTH_ON_BEHALF_OF_FLOW, ConnectionType.OAUTH2_CLIENT_CREDS, ConnectionType.OAUTH2_AUTH_CODE} and (
168
157
  args.get('token_url') is None
169
158
  ):
170
159
  raise typer.BadParameter(
171
160
  f"Missing flags --token-url is required for type {type}"
172
161
  )
173
162
 
163
+
174
164
  if type == ConnectionType.OAUTH_ON_BEHALF_OF_FLOW and (
175
165
  args.get('grant_type') is None
176
166
  ):
@@ -201,19 +191,21 @@ def _get_credentials(type: ConnectionType, **kwargs):
201
191
  return APIKeyAuthCredentials(
202
192
  api_key=kwargs.get("api_key")
203
193
  )
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
- # )
194
+ case ConnectionType.OAUTH2_AUTH_CODE:
195
+ return OAuth2AuthCodeCredentials(
196
+ authorization_url=kwargs.get("auth_url"),
197
+ client_id=kwargs.get("client_id"),
198
+ client_secret=kwargs.get("client_secret"),
199
+ token_url=kwargs.get("token_url"),
200
+ scopes=kwargs.get("scopes")
201
+ )
202
+ case ConnectionType.OAUTH2_CLIENT_CREDS:
203
+ return OAuth2ClientCredentials(
204
+ client_id=kwargs.get("client_id"),
205
+ client_secret=kwargs.get("client_secret"),
206
+ token_url=kwargs.get("token_url"),
207
+ scopes=kwargs.get("scopes")
208
+ )
217
209
  # case ConnectionType.OAUTH2_IMPLICIT:
218
210
  # return OAuth2ImplicitCredentials(
219
211
  # authorization_url=kwargs.get("auth_url"),
@@ -268,11 +260,14 @@ def add_configuration(config: ConnectionConfiguration) -> None:
268
260
  logger.warning(f"Detected a change in sso from '{existing_configuration.sso}' to '{config.sso}'. The associated credentials will be removed.")
269
261
  should_delete_credentials = True
270
262
 
263
+ existing_conn_type = get_connection_type(security_scheme=existing_configuration.security_scheme, auth_type=existing_configuration.auth_type)
264
+ use_app_credentials = existing_conn_type in OAUTH_CONNECTION_TYPES
265
+
271
266
  if should_delete_credentials:
272
267
  try:
273
- existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=existing_configuration.sso)
268
+ existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials)
274
269
  if existing_credentials:
275
- client.delete_credentials(app_id=app_id, env=environment, use_sso=existing_configuration.sso)
270
+ client.delete_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials)
276
271
  except:
277
272
  logger.error(f"Error removing credentials for connection '{app_id}' in environment '{environment}'. No changes have been made to the configuration.")
278
273
  sys.exit(1)
@@ -290,11 +285,11 @@ def add_configuration(config: ConnectionConfiguration) -> None:
290
285
  logger.error(response_text)
291
286
  exit(1)
292
287
 
293
- def add_credentials(app_id: str, environment: ConnectionEnvironment, use_sso: bool, credentials: CREDENTIALS) -> None:
288
+ def add_credentials(app_id: str, environment: ConnectionEnvironment, use_app_credentials: bool, credentials: CREDENTIALS) -> None:
294
289
  client = get_connections_client()
295
290
  try:
296
- existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=use_sso)
297
- if use_sso:
291
+ existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials)
292
+ if use_app_credentials:
298
293
  payload = {
299
294
  "app_credentials": credentials.model_dump(exclude_none=True)
300
295
  }
@@ -305,9 +300,9 @@ def add_credentials(app_id: str, environment: ConnectionEnvironment, use_sso: bo
305
300
 
306
301
  logger.info(f"Setting credentials for environment '{environment}' on connection '{app_id}'")
307
302
  if existing_credentials:
308
- client.update_credentials(app_id=app_id, env=environment, use_sso=use_sso, payload=payload)
303
+ client.update_credentials(app_id=app_id, env=environment, use_app_credentials=use_app_credentials, payload=payload)
309
304
  else:
310
- client.create_credentials(app_id=app_id,env=environment, use_sso=use_sso, payload=payload)
305
+ client.create_credentials(app_id=app_id,env=environment, use_app_credentials=use_app_credentials, payload=payload)
311
306
  logger.info(f"Credentials successfully set for '{environment}' environment of connection '{app_id}'")
312
307
  except requests.HTTPError as e:
313
308
  response = e.response
@@ -319,7 +314,7 @@ def add_identity_provider(app_id: str, environment: ConnectionEnvironment, idp:
319
314
  client = get_connections_client()
320
315
 
321
316
  try:
322
- existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_sso=True)
317
+ existing_credentials = client.get_credentials(app_id=app_id, env=environment, use_app_credentials=True)
323
318
 
324
319
  payload = {
325
320
  "idp_credentials": idp.model_dump()
@@ -327,9 +322,9 @@ def add_identity_provider(app_id: str, environment: ConnectionEnvironment, idp:
327
322
 
328
323
  logger.info(f"Setting identity provider for environment '{environment}' on connection '{app_id}'")
329
324
  if existing_credentials:
330
- client.update_credentials(app_id=app_id, env=environment, use_sso=True, payload=payload)
325
+ client.update_credentials(app_id=app_id, env=environment, use_app_credentials=True, payload=payload)
331
326
  else:
332
- client.create_credentials(app_id=app_id,env=environment, use_sso=True, payload=payload)
327
+ client.create_credentials(app_id=app_id,env=environment, use_app_credentials=True, payload=payload)
333
328
  logger.info(f"Identity provider successfully set for '{environment}' environment of connection '{app_id}'")
334
329
  except requests.HTTPError as e:
335
330
  response = e.response
@@ -388,11 +383,17 @@ def list_connections(environment: ConnectionEnvironment | None, verbose: bool =
388
383
  non_configured_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="*Non-Configured")
389
384
  draft_table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True, title="Draft")
390
385
  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)
386
+ default_args = {"justify": "center", "no_wrap": True}
387
+ column_args = {
388
+ "App ID": {"overflow": "fold"},
389
+ "Auth Type": {},
390
+ "Type": {},
391
+ "Credentials Set/ Connected": {}
392
+ }
393
+ for column in column_args:
394
+ draft_table.add_column(column,**default_args, **column_args[column])
395
+ live_table.add_column(column,**default_args, **column_args[column])
396
+ non_configured_table.add_column(column,**default_args, **column_args[column])
396
397
 
397
398
  for conn in connections:
398
399
  if conn.environment is None:
@@ -437,6 +438,7 @@ def configure_connection(**kwargs) -> None:
437
438
  logger.error(f"Cannot create configuration for environment '{kwargs.get('environment')}'. Local development does not support any environments other than 'draft'.")
438
439
  sys.exit(1)
439
440
 
441
+
440
442
  idp_config_body = None
441
443
  if kwargs.get("idp_token_type") or kwargs.get("idp_token_use"):
442
444
  idp_config_body = IdpConfigDataBody(
@@ -463,11 +465,6 @@ def configure_connection(**kwargs) -> None:
463
465
 
464
466
  config = ConnectionConfiguration.model_validate(kwargs)
465
467
 
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
468
  add_configuration(config)
472
469
 
473
470
  def set_credentials_connection(
@@ -484,11 +481,12 @@ def set_credentials_connection(
484
481
 
485
482
  sso_enabled = config.sso
486
483
  conn_type = get_connection_type(security_scheme=config.security_scheme, auth_type=config.auth_type)
484
+ use_app_credentials = conn_type in OAUTH_CONNECTION_TYPES
487
485
 
488
486
  _validate_connection_params(type=conn_type, **kwargs)
489
487
  credentials = _get_credentials(type=conn_type, **kwargs)
490
488
 
491
- add_credentials(app_id=app_id, environment=environment, use_sso=sso_enabled, credentials=credentials)
489
+ add_credentials(app_id=app_id, environment=environment, use_app_credentials=use_app_credentials, credentials=credentials)
492
490
 
493
491
  def set_identity_provider_connection(
494
492
  app_id: str,
@@ -5,6 +5,7 @@ from ibm_watsonx_orchestrate.cli.commands.environment import environment_control
5
5
  from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
6
6
  from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
7
7
  from ibm_watsonx_orchestrate.client.utils import is_local_dev
8
+ import sys
8
9
 
9
10
  logger = logging.getLogger(__name__)
10
11
 
@@ -20,7 +21,19 @@ def activate_env(
20
21
  apikey: Annotated[
21
22
  str,
22
23
  typer.Option(
23
- "--apikey", "-a", help="WXO API Key. Leave Blank if developing locally"
24
+ "--api-key", "-a", help="WXO or CPD API Key. Leave Blank if developing locally. For CPD, either a Passoword or Apikey is accepted for CPD, but not both."
25
+ ),
26
+ ] = None,
27
+ username: Annotated[
28
+ str,
29
+ typer.Option(
30
+ "--username", "-u", help="Username specifically for CPD Environments."
31
+ ),
32
+ ] = None,
33
+ password: Annotated[
34
+ str,
35
+ typer.Option(
36
+ "--password", "-p", help="Password specifically for CPD Environments. Either a Passoword or Apikey is accepted for CPD, but not both."
24
37
  ),
25
38
  ] = None,
26
39
  registry: Annotated[
@@ -32,7 +45,7 @@ def activate_env(
32
45
  typer.Option("--test-package-version-override", help="Which prereleased package version to reference when using --registry testpypi", hidden=True),
33
46
  ] = None
34
47
  ):
35
- environment_controller.activate(name=name, apikey=apikey, registry=registry, test_package_version_override=test_package_version_override)
48
+ environment_controller.activate(name=name, apikey=apikey,username=username, password=password, registry=registry, test_package_version_override=test_package_version_override)
36
49
 
37
50
 
38
51
  @environment_app.command(name="add")
@@ -58,9 +71,21 @@ def add_env(
58
71
  type: Annotated[
59
72
  EnvironmentAuthType,
60
73
  typer.Option("--type", "-t", help="The type of auth you wish to use"),
61
- ] = None
74
+ ] = None,
75
+ insecure: Annotated[
76
+ bool,
77
+ typer.Option("--insecure", help="Ignore SSL validation errors. Used for CPD Environments only"),
78
+ ] = False,
79
+ verify: Annotated[
80
+ str,
81
+ typer.Option("--verify", help="Path to SSL Cert. Used for CPD Environments only"),
82
+ ] = None,
62
83
  ):
63
- environment_controller.add(name=name, url=url, should_activate=activate, iam_url=iam_url, type=type)
84
+ if insecure and verify:
85
+ logger.error("Please choose either '--insecure' or '--verify' but not both.")
86
+ sys.exit(1)
87
+
88
+ environment_controller.add(name=name, url=url, should_activate=activate, iam_url=iam_url, type=type, insecure=insecure, verify=verify)
64
89
 
65
90
 
66
91
  @environment_app.command(name="remove")
@@ -2,6 +2,7 @@ import logging
2
2
  import rich
3
3
  import jwt
4
4
  import getpass
5
+ import sys
5
6
 
6
7
  from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
7
8
  from ibm_watsonx_orchestrate.cli.config import (
@@ -17,14 +18,15 @@ from ibm_watsonx_orchestrate.cli.config import (
17
18
  ENV_IAM_URL_OPT,
18
19
  ENVIRONMENTS_SECTION_HEADER,
19
20
  PROTECTED_ENV_NAME,
20
- ENV_AUTH_TYPE, PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT,
21
+ ENV_AUTH_TYPE, PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TYPE_OPT, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT, BYPASS_SSL, VERIFY,
21
22
  DEFAULT_CONFIG_FILE_CONTENT
22
23
  )
23
24
  from ibm_watsonx_orchestrate.client.client import Client
24
25
  from ibm_watsonx_orchestrate.client.client_errors import ClientError
26
+ from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient, ClientAPIException
25
27
  from ibm_watsonx_orchestrate.client.credentials import Credentials
26
28
  from threading import Lock
27
- from ibm_watsonx_orchestrate.client.utils import is_local_dev, check_token_validity
29
+ from ibm_watsonx_orchestrate.client.utils import is_local_dev, check_token_validity, is_cpd_env
28
30
  from ibm_watsonx_orchestrate.cli.commands.environment.types import EnvironmentAuthType
29
31
 
30
32
  logger = logging.getLogger(__name__)
@@ -42,7 +44,33 @@ def _decode_token(token: str, is_local: bool = False) -> dict:
42
44
  logger.error("Invalid token format")
43
45
  raise e
44
46
 
45
- def _login(name: str, apikey: str = None) -> None:
47
+
48
+ def _validate_token_functionality(token: str, url: str) -> None:
49
+ '''
50
+ Validates a token by making a request to GET /agents
51
+
52
+ Args:
53
+ token: A JWT token
54
+ url: WXO instance URL
55
+ '''
56
+ is_cpd = is_cpd_env(url)
57
+ if is_cpd is True:
58
+ agent_client = AgentClient(base_url=url, api_key=token, is_local=is_local_dev(url), verify=False)
59
+ else:
60
+ agent_client = AgentClient(base_url=url, api_key=token, is_local=is_local_dev(url))
61
+ agent_client.api_key = token
62
+
63
+ try:
64
+ agent_client.get()
65
+ except ClientAPIException as e:
66
+ if e.response.status_code >= 400:
67
+ reason = e.response.reason
68
+ logger.error(f"Failed to authenticate to provided instance '{url}'. Reason: '{reason}'. Please ensure provider URL and API key are valid.")
69
+ sys.exit(1)
70
+ raise e
71
+
72
+
73
+ def _login(name: str, apikey: str = None, username: str = None, password: str = None) -> None:
46
74
  cfg = Config()
47
75
  auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
48
76
 
@@ -55,14 +83,43 @@ def _login(name: str, apikey: str = None) -> None:
55
83
  except (KeyError, AttributeError):
56
84
  auth_type = None
57
85
 
86
+ username = username
87
+ apikey = apikey
88
+ password = password
89
+
90
+ if is_cpd_env(url):
91
+ if username is None:
92
+ username = getpass.getpass("Please enter CPD Username: ")
93
+
94
+ if not apikey and not password:
95
+ apikey = getpass.getpass("Enter CPD API key (or leave blank to use password): ")
96
+ if not apikey and not password:
97
+ password = getpass.getpass("Enter CPD password (or leave blank if you used API key): ")
98
+
99
+ if apikey and password:
100
+ logger.error("For CPD, please use either an Apikey or a Password but not both.")
101
+ sys.exit(1)
102
+
103
+ if not apikey and not password:
104
+ logger.error("For CPD, you must provide either an API key or a password.")
105
+ sys.exit(1)
106
+
58
107
 
59
- if apikey is None and not is_local:
108
+ if not apikey and not password and not is_local and auth_type != "cpd":
60
109
  apikey = getpass.getpass("Please enter WXO API key: ")
61
110
 
62
111
  try:
63
- creds = Credentials(url=url, api_key=apikey, iam_url=iam_url, auth_type=auth_type)
112
+ creds = Credentials(
113
+ url=url,
114
+ api_key=apikey,
115
+ username=username,
116
+ password=password,
117
+ iam_url=iam_url,
118
+ auth_type=auth_type
119
+ )
64
120
  client = Client(creds)
65
121
  token = _decode_token(client.token, is_local)
122
+ _validate_token_functionality(token=token.get(AUTH_MCSP_TOKEN_OPT), url=url)
66
123
  with lock:
67
124
  auth_cfg.save(
68
125
  {
@@ -74,7 +131,7 @@ def _login(name: str, apikey: str = None) -> None:
74
131
  except ClientError as e:
75
132
  raise ClientError(e)
76
133
 
77
- def activate(name: str, apikey: str=None, registry: RegistryType=None, test_package_version_override=None) -> None:
134
+ def activate(name: str, apikey: str=None, username: str=None, password: str=None, registry: RegistryType=None, test_package_version_override=None) -> None:
78
135
  cfg = Config()
79
136
  auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
80
137
  env_cfg = cfg.read(ENVIRONMENTS_SECTION_HEADER, name)
@@ -92,7 +149,7 @@ def activate(name: str, apikey: str=None, registry: RegistryType=None, test_pack
92
149
  existing_token = existing_auth_config.get(AUTH_MCSP_TOKEN_OPT) if existing_auth_config else None
93
150
 
94
151
  if not check_token_validity(existing_token) or is_local:
95
- _login(name=name, apikey=apikey)
152
+ _login(name=name, apikey=apikey, username=username, password=password)
96
153
 
97
154
  with lock:
98
155
  cfg.write(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT, name)
@@ -104,8 +161,11 @@ def activate(name: str, apikey: str=None, registry: RegistryType=None, test_pack
104
161
  cfg.write(PYTHON_REGISTRY_HEADER, PYTHON_REGISTRY_TEST_PACKAGE_VERSION_OVERRIDE_OPT, test_package_version_override)
105
162
 
106
163
  logger.info(f"Environment '{name}' is now active")
164
+ is_cpd = is_cpd_env(url)
165
+ if is_cpd:
166
+ logger.warning("Support for CPD clusters is currently an early access preview")
107
167
 
108
- def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, type: EnvironmentAuthType=None) -> None:
168
+ def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, type: EnvironmentAuthType=None, insecure: bool=None, verify: str=None) -> None:
109
169
  if name == PROTECTED_ENV_NAME:
110
170
  logger.error(f"The name '{PROTECTED_ENV_NAME}' is a reserved environment name. Please select a diffrent name or use `orchestrate env activate {PROTECTED_ENV_NAME}` to swap to '{PROTECTED_ENV_NAME}'")
111
171
  return
@@ -124,6 +184,12 @@ def add(name: str, url: str, should_activate: bool=False, iam_url: str=None, typ
124
184
  cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {ENV_IAM_URL_OPT: iam_url})
125
185
  if type:
126
186
  cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {ENV_AUTH_TYPE: str(type)})
187
+ if insecure:
188
+ cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {BYPASS_SSL: insecure})
189
+ cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {VERIFY: 'None'})
190
+ if verify:
191
+ cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {VERIFY: verify})
192
+ cfg.write(ENVIRONMENTS_SECTION_HEADER, name, {BYPASS_SSL: False})
127
193
 
128
194
 
129
195
  logger.info(f"Environment '{name}' has been created")
@@ -4,6 +4,7 @@ from enum import Enum
4
4
  class EnvironmentAuthType(str, Enum):
5
5
  IBM_CLOUD_IAM = 'ibm_iam'
6
6
  MCSP = 'mcsp'
7
+ CPD = 'cpd'
7
8
 
8
9
  def __str__(self):
9
10
  return self.value