ibm-watsonx-orchestrate 1.6.0b0__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 (37) hide show
  1. ibm_watsonx_orchestrate/__init__.py +1 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/agent.py +1 -0
  3. ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -1
  4. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/__init__.py +2 -0
  5. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/prompts.py +33 -0
  6. ibm_watsonx_orchestrate/agent_builder/agents/webchat_customizations/welcome_content.py +20 -0
  7. ibm_watsonx_orchestrate/agent_builder/connections/__init__.py +2 -2
  8. ibm_watsonx_orchestrate/agent_builder/connections/types.py +38 -31
  9. ibm_watsonx_orchestrate/agent_builder/tools/flow_tool.py +83 -0
  10. ibm_watsonx_orchestrate/agent_builder/tools/types.py +7 -1
  11. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +25 -14
  12. ibm_watsonx_orchestrate/cli/commands/channels/webchat/channels_webchat_controller.py +104 -21
  13. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +26 -18
  14. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +55 -57
  15. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +2 -2
  16. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +137 -10
  17. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +9 -3
  18. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +25 -12
  19. ibm_watsonx_orchestrate/client/agents/agent_client.py +74 -6
  20. ibm_watsonx_orchestrate/client/base_api_client.py +2 -1
  21. ibm_watsonx_orchestrate/client/connections/connections_client.py +18 -9
  22. ibm_watsonx_orchestrate/client/connections/utils.py +4 -2
  23. ibm_watsonx_orchestrate/client/local_service_instance.py +1 -1
  24. ibm_watsonx_orchestrate/client/service_instance.py +3 -3
  25. ibm_watsonx_orchestrate/client/tools/tempus_client.py +8 -3
  26. ibm_watsonx_orchestrate/client/utils.py +10 -0
  27. ibm_watsonx_orchestrate/docker/compose-lite.yml +400 -66
  28. ibm_watsonx_orchestrate/docker/default.env +44 -12
  29. ibm_watsonx_orchestrate/docker/proxy-config-single.yaml +12 -0
  30. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +15 -5
  31. ibm_watsonx_orchestrate/flow_builder/utils.py +78 -48
  32. ibm_watsonx_orchestrate/run/connections.py +4 -4
  33. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/METADATA +1 -1
  34. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/RECORD +37 -32
  35. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/WHEEL +0 -0
  36. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/entry_points.txt +0 -0
  37. {ibm_watsonx_orchestrate-1.6.0b0.dist-info → ibm_watsonx_orchestrate-1.7.0a0.dist-info}/licenses/LICENSE +0 -0
@@ -240,6 +240,27 @@ def apply_llm_api_key_defaults(env_dict: dict) -> None:
240
240
  env_dict.setdefault("ASSISTANT_EMBEDDINGS_SPACE_ID", space_value)
241
241
  env_dict.setdefault("ROUTING_LLM_SPACE_ID", space_value)
242
242
 
243
+ def _is_docker_container_running(container_name):
244
+ ensure_docker_installed()
245
+ command = [ "docker",
246
+ "ps",
247
+ "-f",
248
+ f"name={container_name}"
249
+ ]
250
+ result = subprocess.run(command, env=os.environ, capture_output=True)
251
+ if container_name in str(result.stdout):
252
+ return True
253
+ return False
254
+
255
+ def _check_exclusive_observibility(langfuse_enabled: bool, ibm_tele_enabled: bool):
256
+ if langfuse_enabled and ibm_tele_enabled:
257
+ return False
258
+ if langfuse_enabled and _is_docker_container_running("docker-frontend-server-1"):
259
+ return False
260
+ if ibm_tele_enabled and _is_docker_container_running("docker-langfuse-web-1"):
261
+ return False
262
+ return True
263
+
243
264
  def write_merged_env_file(merged_env: dict) -> Path:
244
265
  tmp = tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".env")
245
266
  with tmp:
@@ -292,7 +313,7 @@ def get_persisted_user_env() -> dict | None:
292
313
  user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
293
314
  return user_env
294
315
 
295
- def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) -> None:
316
+ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, experimental_with_ibm_telemetry=False, with_docproc=False) -> None:
296
317
  compose_path = get_compose_file()
297
318
  compose_command = ensure_docker_compose_installed()
298
319
  db_tag = read_env_file(final_env_file).get('DBTAG', None)
@@ -319,13 +340,17 @@ def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False) ->
319
340
 
320
341
 
321
342
  # Step 2: Start all remaining services (except DB)
343
+ profiles = []
322
344
  if experimental_with_langfuse:
323
- command = compose_command + [
324
- '--profile',
325
- 'langfuse'
326
- ]
327
- else:
328
- command = compose_command
345
+ profiles.append("langfuse")
346
+ if experimental_with_ibm_telemetry:
347
+ profiles.append("ibm-telemetry")
348
+ if with_docproc:
349
+ profiles.append("docproc")
350
+
351
+ command = compose_command[:]
352
+ for profile in profiles:
353
+ command += ["--profile", profile]
329
354
 
330
355
  command += [
331
356
  "-f", str(compose_path),
@@ -444,6 +469,9 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
444
469
  # do nothing, as the docker login here is not mandatory
445
470
  pass
446
471
 
472
+ # Auto-configure callback IP for async tools
473
+ merged_env_dict = auto_configure_callback_ip(merged_env_dict)
474
+
447
475
  #These are to removed warning and not used in UI component
448
476
  if not 'WATSONX_SPACE_ID' in merged_env_dict:
449
477
  merged_env_dict['WATSONX_SPACE_ID']='X'
@@ -621,8 +649,80 @@ def confirm_accepts_license_agreement(accepts_by_argument: bool):
621
649
  logger.error('The terms and conditions were not accepted, exiting.')
622
650
  exit(1)
623
651
 
652
+ def auto_configure_callback_ip(merged_env_dict: dict) -> dict:
653
+ """
654
+ Automatically detect and configure CALLBACK_HOST_URL if it's empty.
655
+
656
+ Args:
657
+ merged_env_dict: The merged environment dictionary
658
+
659
+ Returns:
660
+ Updated environment dictionary with CALLBACK_HOST_URL set
661
+ """
662
+ callback_url = merged_env_dict.get('CALLBACK_HOST_URL', '').strip()
663
+
664
+ # Only auto-configure if CALLBACK_HOST_URL is empty
665
+ if not callback_url:
666
+ logger.info("Auto-detecting local IP address for async tool callbacks...")
624
667
 
668
+ system = platform.system()
669
+ ip = None
625
670
 
671
+ try:
672
+ if system in ("Linux", "Darwin"):
673
+ result = subprocess.run(["ifconfig"], capture_output=True, text=True, check=True)
674
+ lines = result.stdout.splitlines()
675
+
676
+ for line in lines:
677
+ line = line.strip()
678
+ # Unix ifconfig output format: "inet 192.168.1.100 netmask 0xffffff00 broadcast 192.168.1.255"
679
+ if line.startswith("inet ") and "127.0.0.1" not in line:
680
+ candidate_ip = line.split()[1]
681
+ # Validate IP is not loopback or link-local
682
+ if (candidate_ip and
683
+ not candidate_ip.startswith("127.") and
684
+ not candidate_ip.startswith("169.254")):
685
+ ip = candidate_ip
686
+ break
687
+
688
+ elif system == "Windows":
689
+ result = subprocess.run(["ipconfig"], capture_output=True, text=True, check=True)
690
+ lines = result.stdout.splitlines()
691
+
692
+ for line in lines:
693
+ line = line.strip()
694
+ # Windows ipconfig output format: " IPv4 Address. . . . . . . . . . . : 192.168.1.100"
695
+ if "IPv4 Address" in line and ":" in line:
696
+ candidate_ip = line.split(":")[-1].strip()
697
+ # Validate IP is not loopback or link-local
698
+ if (candidate_ip and
699
+ not candidate_ip.startswith("127.") and
700
+ not candidate_ip.startswith("169.254")):
701
+ ip = candidate_ip
702
+ break
703
+
704
+ else:
705
+ logger.warning(f"Unsupported platform: {system}")
706
+ ip = None
707
+
708
+ except Exception as e:
709
+ logger.debug(f"IP detection failed on {system}: {e}")
710
+ ip = None
711
+
712
+ if ip:
713
+ callback_url = f"http://{ip}:4321"
714
+ merged_env_dict['CALLBACK_HOST_URL'] = callback_url
715
+ logger.info(f"Auto-configured CALLBACK_HOST_URL to: {callback_url}")
716
+ else:
717
+ # Fallback for localhost
718
+ callback_url = "http://host.docker.internal:4321"
719
+ merged_env_dict['CALLBACK_HOST_URL'] = callback_url
720
+ logger.info(f"Using Docker internal URL: {callback_url}")
721
+ logger.info("For external tools, consider using ngrok or similar tunneling service.")
722
+ else:
723
+ logger.info(f"Using existing CALLBACK_HOST_URL: {callback_url}")
724
+
725
+ return merged_env_dict
626
726
 
627
727
  @server_app.command(name="start")
628
728
  def server_start(
@@ -636,6 +736,11 @@ def server_start(
636
736
  '--with-langfuse', '-l',
637
737
  help='Option to enable Langfuse support.'
638
738
  ),
739
+ experimental_with_ibm_telemetry: bool = typer.Option(
740
+ False,
741
+ '--with-ibm-telemetry', '-i',
742
+ help=''
743
+ ),
639
744
  persist_env_secrets: bool = typer.Option(
640
745
  False,
641
746
  '--persist-env-secrets', '-p',
@@ -647,6 +752,11 @@ def server_start(
647
752
  "--accept-terms-and-conditions",
648
753
  help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
649
754
  ),
755
+ with_docproc: bool = typer.Option(
756
+ False,
757
+ '--with-docproc', '-d',
758
+ help='Enable IBM Document Processing to extract information from your business documents. Enabling this activates the Watson Document Understanding service.'
759
+ ),
650
760
  ):
651
761
  confirm_accepts_license_agreement(accept_terms_and_conditions)
652
762
 
@@ -675,10 +785,21 @@ def server_start(
675
785
 
676
786
  merged_env_dict = apply_server_env_dict_defaults(merged_env_dict)
677
787
 
678
- # Add LANGFUSE_ENABLED into the merged_env_dict, for tempus to pick up.
788
+ # Auto-configure callback IP for async tools
789
+ merged_env_dict = auto_configure_callback_ip(merged_env_dict)
790
+ if not _check_exclusive_observibility(experimental_with_langfuse, experimental_with_ibm_telemetry):
791
+ logger.error("Please select either langfuse or ibm telemetry for observability not both")
792
+ sys.exit(1)
793
+
794
+ # Add LANGFUSE_ENABLED and DOCPROC_ENABLED into the merged_env_dict, for tempus to pick up.
679
795
  if experimental_with_langfuse:
680
796
  merged_env_dict['LANGFUSE_ENABLED'] = 'true'
681
797
 
798
+ if with_docproc:
799
+ merged_env_dict['DOCPROC_ENABLED'] = 'true'
800
+
801
+ if experimental_with_ibm_telemetry:
802
+ merged_env_dict['USE_IBM_TELEMETRY'] = 'true'
682
803
 
683
804
  try:
684
805
  docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
@@ -690,7 +811,11 @@ def server_start(
690
811
 
691
812
 
692
813
  final_env_file = write_merged_env_file(merged_env_dict)
693
- run_compose_lite(final_env_file=final_env_file, experimental_with_langfuse=experimental_with_langfuse)
814
+
815
+ run_compose_lite(final_env_file=final_env_file,
816
+ experimental_with_langfuse=experimental_with_langfuse,
817
+ experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
818
+ with_docproc=with_docproc)
694
819
 
695
820
  run_db_migration()
696
821
 
@@ -718,6 +843,8 @@ def server_start(
718
843
 
719
844
  if experimental_with_langfuse:
720
845
  logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
846
+ if with_docproc:
847
+ logger.info(f"Document processing capabilities are now available for use in Flows (both ADK and runtime). Note: This option is currently available only in the Developer edition.")
721
848
 
722
849
  @server_app.command(name="stop")
723
850
  def server_stop(
@@ -825,4 +952,4 @@ def run_db_migration() -> None:
825
952
  sys.exit(1)
826
953
 
827
954
  if __name__ == "__main__":
828
- server_app()
955
+ server_app()
@@ -243,9 +243,15 @@ class ToolkitController:
243
243
  rich.print(JSON(json.dumps(tools_list, indent=4)))
244
244
  else:
245
245
  table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
246
- columns = ["Name", "Kind", "Description", "Tools", "App ID"]
247
- for column in columns:
248
- table.add_column(column)
246
+ column_args = {
247
+ "Name": {"overflow": "fold"},
248
+ "Kind": {},
249
+ "Description": {},
250
+ "Tools": {},
251
+ "App ID": {"overflow": "fold"}
252
+ }
253
+ for column in column_args:
254
+ table.add_column(column,**column_args[column])
249
255
 
250
256
  tools_client = instantiate_client(ToolClient)
251
257
 
@@ -24,6 +24,7 @@ from rich.console import Console
24
24
  from rich.panel import Panel
25
25
 
26
26
  from ibm_watsonx_orchestrate.agent_builder.tools import BaseTool, ToolSpec
27
+ from ibm_watsonx_orchestrate.agent_builder.tools.flow_tool import create_flow_json_tool
27
28
  from ibm_watsonx_orchestrate.agent_builder.tools.openapi_tool import create_openapi_json_tools_from_uri,create_openapi_json_tools_from_content
28
29
  from ibm_watsonx_orchestrate.cli.commands.models.models_controller import ModelHighlighter
29
30
  from ibm_watsonx_orchestrate.cli.commands.tools.types import RegistryType
@@ -38,10 +39,9 @@ from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
38
39
  from ibm_watsonx_orchestrate.client.toolkit.toolkit_client import ToolKitClient
39
40
  from ibm_watsonx_orchestrate.client.connections import get_connections_client, get_connection_type
40
41
  from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
42
+ from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_support_tools
41
43
  from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
42
44
  from ibm_watsonx_orchestrate.client.utils import is_local_dev
43
- from ibm_watsonx_orchestrate.client.tools.tempus_client import TempusClient
44
- from ibm_watsonx_orchestrate.flow_builder.utils import import_flow_model
45
45
 
46
46
  from ibm_watsonx_orchestrate import __version__
47
47
 
@@ -345,17 +345,12 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
345
345
 
346
346
  [bold cyan]Additional information:[/bold cyan]
347
347
 
348
- - The [bold green]get_flow_status[/bold green] tool is being imported to support flow tools. To get a flow's current status, ensure [bold]both this tools and the one you are importing are added to your agent[/bold] to retrieve the flow output.
349
- - Include additional instructions in your agent to call the [bold green]get_flow_status[/bold green] tool to retrieve the flow output. For example: [green]"If you get an instance_id, use the tool get_flow_status to retrieve the current status of a flow."[/green]
348
+ - The [bold green]Get flow status[/bold green] tool is being imported to support flow tools. This tool can query the status of a flow tool instance. You can add it to your agent using the UI or including the following tool name in your agent definition: [green]i__get_flow_status_intrinsic_tool__[/green].
350
349
 
351
350
  """
352
351
 
353
352
  console.print(Panel(message, title="[bold blue]Flow tool support information[/bold blue]", border_style="bright_blue"))
354
353
 
355
-
356
- if not is_local_dev():
357
- raise typer.BadParameter(f"Flow tools are only supported in local environment.")
358
-
359
354
  model = None
360
355
 
361
356
  # Load the Flow JSON model from the file
@@ -422,7 +417,16 @@ The [bold]flow tool[/bold] is being imported from [green]`{file}`[/green].
422
417
  except Exception as e:
423
418
  raise typer.BadParameter(f"Failed to load model from file {file}: {e}")
424
419
 
425
- return await import_flow_model(model)
420
+ tool = create_flow_json_tool(name=model["spec"]["name"],
421
+ description=model["spec"]["description"],
422
+ permission="read_only",
423
+ flow_model=model)
424
+
425
+ tools = import_flow_support_tools()
426
+
427
+ tools.append(tool)
428
+
429
+ return tools
426
430
 
427
431
 
428
432
  async def import_openapi_tool(file: str, connection_id: str) -> List[BaseTool]:
@@ -514,9 +518,16 @@ class ToolsController:
514
518
  rich.print(JSON(json.dumps(tools_list, indent=4)))
515
519
  else:
516
520
  table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
517
- columns = ["Name", "Description", "Permission", "Type", "Toolkit", "App ID"]
518
- for column in columns:
519
- table.add_column(column)
521
+ column_args = {
522
+ "Name": {"overflow": "fold"},
523
+ "Description": {},
524
+ "Permission": {},
525
+ "Type": {},
526
+ "Toolkit": {},
527
+ "App ID": {"overflow": "fold"}
528
+ }
529
+ for column in column_args:
530
+ table.add_column(column,**column_args[column])
520
531
 
521
532
  connections_client = get_connections_client()
522
533
  connections = connections_client.list()
@@ -558,6 +569,8 @@ class ToolsController:
558
569
  tool_type=ToolKind.openapi
559
570
  elif tool_binding.mcp is not None:
560
571
  tool_type=ToolKind.mcp
572
+ elif tool_binding.flow is not None:
573
+ tool_type=ToolKind.flow
561
574
  else:
562
575
  tool_type="Unknown"
563
576
 
@@ -3,6 +3,74 @@ from typing_extensions import List, Optional
3
3
  from ibm_watsonx_orchestrate.client.utils import is_local_dev
4
4
  from pydantic import BaseModel
5
5
 
6
+ def transform_agents_from_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
7
+ if isinstance(agents,list):
8
+ new_agents = []
9
+ for agent in agents:
10
+ new_agents.append(_transform_agent_from_flat_agent_spec(agent))
11
+ agents = new_agents
12
+ else:
13
+ agents = _transform_agent_from_flat_agent_spec(agents)
14
+
15
+ return agents
16
+
17
+
18
+ def _transform_agent_from_flat_agent_spec(agent_spec: dict ) -> dict:
19
+ transformed = {"additional_properties": {}}
20
+ for key,value in agent_spec.items():
21
+ if key == "starter_prompts":
22
+ if value:
23
+ value.pop("is_default_prompts",None)
24
+ value["customize"] = value.pop("prompts", [])
25
+
26
+ transformed["additional_properties"] |= { key: value }
27
+
28
+ elif key == "welcome_content":
29
+ if value:
30
+ value.pop("is_default_message", None)
31
+
32
+ transformed["additional_properties"] |= { key: value }
33
+
34
+ else:
35
+ transformed |= { key: value }
36
+
37
+ return transformed
38
+
39
+ def transform_agents_to_flat_agent_spec(agents: dict | list[dict] ) -> dict | list[dict]:
40
+ if isinstance(agents,list):
41
+ new_agents = []
42
+ for agent in agents:
43
+ new_agents.append(_transform_agent_to_flat_agent_spec(agent))
44
+ agents = new_agents
45
+ else:
46
+ agents = _transform_agent_to_flat_agent_spec(agents)
47
+
48
+ return agents
49
+
50
+ def _transform_agent_to_flat_agent_spec(agent_spec: dict ) -> dict:
51
+ additional_properties = agent_spec.get("additional_properties", None)
52
+ if not additional_properties:
53
+ return agent_spec
54
+
55
+ transformed = agent_spec
56
+ for key,value in additional_properties.items():
57
+ if key == "starter_prompts":
58
+ if value:
59
+ value["is_default_prompts"] = False
60
+ value["prompts"] = value.pop("customize", [])
61
+
62
+ transformed[key] = value
63
+
64
+ elif key == "welcome_content":
65
+ if value:
66
+ value["is_default_message"] = False
67
+
68
+ transformed[key] = value
69
+
70
+ transformed.pop("additional_properties",None)
71
+
72
+ return transformed
73
+
6
74
  class AgentUpsertResponse(BaseModel):
7
75
  id: Optional[str] = None
8
76
  warning: Optional[str] = None
@@ -17,14 +85,14 @@ class AgentClient(BaseAPIClient):
17
85
 
18
86
 
19
87
  def create(self, payload: dict) -> AgentUpsertResponse:
20
- response = self._post(self.base_endpoint, data=payload)
88
+ response = self._post(self.base_endpoint, data=transform_agents_from_flat_agent_spec(payload))
21
89
  return AgentUpsertResponse.model_validate(response)
22
90
 
23
91
  def get(self) -> dict:
24
- return self._get(f"{self.base_endpoint}?include_hidden=true")
92
+ return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?include_hidden=true"))
25
93
 
26
94
  def update(self, agent_id: str, data: dict) -> AgentUpsertResponse:
27
- response = self._patch(f"{self.base_endpoint}/{agent_id}", data=data)
95
+ response = self._patch(f"{self.base_endpoint}/{agent_id}", data=transform_agents_from_flat_agent_spec(data))
28
96
  return AgentUpsertResponse.model_validate(response)
29
97
 
30
98
  def delete(self, agent_id: str) -> dict:
@@ -35,14 +103,14 @@ class AgentClient(BaseAPIClient):
35
103
 
36
104
  def get_drafts_by_names(self, agent_names: List[str]) -> List[dict]:
37
105
  formatted_agent_names = [f"names={x}" for x in agent_names]
38
- return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true")
106
+ return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_names)}&include_hidden=true"))
39
107
 
40
108
  def get_draft_by_id(self, agent_id: str) -> List[dict]:
41
109
  if agent_id is None:
42
110
  return ""
43
111
  else:
44
112
  try:
45
- agent = self._get(f"{self.base_endpoint}/{agent_id}")
113
+ agent = transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}/{agent_id}"))
46
114
  return agent
47
115
  except ClientAPIException as e:
48
116
  if e.response.status_code == 404 and "not found with the given name" in e.response.text:
@@ -51,5 +119,5 @@ class AgentClient(BaseAPIClient):
51
119
 
52
120
  def get_drafts_by_ids(self, agent_ids: List[str]) -> List[dict]:
53
121
  formatted_agent_ids = [f"ids={x}" for x in agent_ids]
54
- return self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true")
122
+ return transform_agents_to_flat_agent_spec(self._get(f"{self.base_endpoint}?{'&'.join(formatted_agent_ids)}&include_hidden=true"))
55
123
 
@@ -36,6 +36,8 @@ class BaseAPIClient:
36
36
 
37
37
  if not self.is_local:
38
38
  self.base_url = f"{self.base_url}/v1/orchestrate"
39
+ else:
40
+ self.base_url = f"{self.base_url}/v1"
39
41
 
40
42
  def _get_headers(self) -> dict:
41
43
  headers = {}
@@ -46,7 +48,6 @@ class BaseAPIClient:
46
48
  return headers
47
49
 
48
50
  def _get(self, path: str, params: dict = None, data=None, return_raw=False) -> dict:
49
-
50
51
  url = f"{self.base_url}{path}"
51
52
  response = requests.get(url, headers=self._get_headers(), params=params, data=data, verify=self.verify)
52
53
  self._check_response(response)
@@ -1,11 +1,12 @@
1
1
  from typing import List
2
2
 
3
+ from ibm_cloud_sdk_core.authenticators import MCSPAuthenticator
3
4
  from pydantic import BaseModel, ValidationError
4
5
  from typing import Optional
5
6
 
6
7
  from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
7
8
  from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment, ConnectionPreference, ConnectionAuthType, ConnectionSecurityScheme, IdpConfigData, AppConfigData, ConnectionType
8
- from ibm_watsonx_orchestrate.client.utils import is_cpd_env
9
+ from ibm_watsonx_orchestrate.client.utils import is_cpd_env, is_local_dev
9
10
 
10
11
  import logging
11
12
  logger = logging.getLogger(__name__)
@@ -42,6 +43,12 @@ class GetConnectionResponse(BaseModel):
42
43
 
43
44
 
44
45
  class ConnectionsClient(BaseAPIClient):
46
+ def __init__(self, base_url: str, api_key: str = None, is_local: bool = False, verify: str = None, authenticator: MCSPAuthenticator = None):
47
+ super(ConnectionsClient, self).__init__(base_url, api_key, is_local, verify, authenticator)
48
+ if is_local_dev(base_url):
49
+ self.base_url = f"{base_url.rstrip('/')}/api/v1/orchestrate"
50
+ else:
51
+ self.base_url = f"{base_url.rstrip('/')}/v1/orchestrate"
45
52
  """
46
53
  Client to handle CRUD operations for Connections endpoint
47
54
  """
@@ -77,6 +84,8 @@ class ConnectionsClient(BaseAPIClient):
77
84
  else f"/connections/applications?include_details=true"
78
85
  )
79
86
  res = self._get(path)
87
+ import json
88
+ json.dumps(res)
80
89
  return [ListConfigsResponse.model_validate(conn) for conn in res.get("applications", [])]
81
90
  except ValidationError as e:
82
91
  logger.error("Recieved unexpected response from server")
@@ -107,25 +116,25 @@ class ConnectionsClient(BaseAPIClient):
107
116
 
108
117
  # POST /api/v1/connections/applications/{app_id}/configs/{env}/credentials
109
118
  # POST /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
110
- def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_sso: bool) -> None:
111
- if use_sso:
119
+ def create_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
120
+ if use_app_credentials:
112
121
  self._post(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
113
122
  else:
114
123
  self._post(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
115
124
 
116
125
  # PATCH /api/v1/connections/applications/{app_id}/configs/{env}/credentials
117
126
  # PATCH /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
118
- def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_sso: bool) -> None:
119
- if use_sso:
127
+ def update_credentials(self, app_id: str, env: ConnectionEnvironment, payload: dict, use_app_credentials: bool) -> None:
128
+ if use_app_credentials:
120
129
  self._patch(f"/connections/applications/{app_id}/configs/{env}/credentials", data=payload)
121
130
  else:
122
131
  self._patch(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials", data=payload)
123
132
 
124
133
  # GET /api/v1/connections/applications/{app_id}/configs/credentials?env={env}
125
134
  # GET /api/v1/connections/applications/{app_id}/configs/runtime_credentials?env={env}
126
- def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_sso: bool) -> dict:
135
+ def get_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> dict:
127
136
  try:
128
- if use_sso:
137
+ if use_app_credentials:
129
138
  path = (
130
139
  f"/connections/applications/{app_id}/credentials?env={env}"
131
140
  if is_cpd_env(self.base_url)
@@ -146,8 +155,8 @@ class ConnectionsClient(BaseAPIClient):
146
155
 
147
156
  # DELETE /api/v1/connections/applications/{app_id}/configs/{env}/credentials
148
157
  # DELETE /api/v1/connections/applications/{app_id}/configs/{env}/runtime_credentials
149
- def delete_credentials(self, app_id: str, env: ConnectionEnvironment, use_sso: bool) -> None:
150
- if use_sso:
158
+ def delete_credentials(self, app_id: str, env: ConnectionEnvironment, use_app_credentials: bool) -> None:
159
+ if use_app_credentials:
151
160
  self._delete(f"/connections/applications/{app_id}/configs/{env}/credentials")
152
161
  else:
153
162
  self._delete(f"/connections/applications/{app_id}/configs/{env}/runtime_credentials")
@@ -17,14 +17,16 @@ def _get_connections_manager_url() -> str:
17
17
  url_parts = url.split(":")
18
18
  url_parts[-1] = str(LOCAL_CONNECTION_MANAGER_PORT)
19
19
  url = ":".join(url_parts)
20
- url = url + "/api/v1/orchestrate"
21
20
  return url
22
21
  return None
23
22
 
24
23
  def get_connections_client() -> ConnectionsClient:
25
24
  return instantiate_client(client=ConnectionsClient, url=_get_connections_manager_url())
26
25
 
27
- def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType:
26
+ def get_connection_type(security_scheme: ConnectionSecurityScheme, auth_type: ConnectionAuthType) -> ConnectionType | None:
27
+ if security_scheme is None and auth_type is None:
28
+ return None
29
+
28
30
  if security_scheme != ConnectionSecurityScheme.OAUTH2:
29
31
  return ConnectionType(security_scheme)
30
32
  return ConnectionType(auth_type)
@@ -14,7 +14,7 @@ DEFAULT_TENANT = {
14
14
  DEFAULT_USER = {"username": "wxo.archer@ibm.com", "password": "watsonx"}
15
15
  DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
16
16
  DEFAULT_LOCAL_AUTH_ENDPOINT = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/auth/token"
17
- DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/tenants"
17
+ DEFAULT_LOCAL_TENANT_URL = f"{DEFAULT_LOCAL_SERVICE_URL}/api/v1/tenants"
18
18
  DEFAULT_LOCAL_TENANT_AUTH_ENDPOINT = "{}/api/v1/auth/token?tenant_id={}"
19
19
 
20
20
 
@@ -51,13 +51,13 @@ class ServiceInstance(BaseServiceInstance):
51
51
  def _create_token(self) -> str:
52
52
  if not self._credentials.auth_type:
53
53
  if ".cloud.ibm.com" in self._credentials.url:
54
- logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mscp' or 'cpd")
54
+ logger.warning("Using IBM IAM Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
55
55
  return self._authenticate(EnvironmentAuthType.IBM_CLOUD_IAM)
56
56
  elif is_cpd_env(self._credentials.url):
57
- logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mscp' or 'cpd")
57
+ logger.warning("Using CPD Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd")
58
58
  return self._authenticate(EnvironmentAuthType.CPD)
59
59
  else:
60
- logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mscp' or 'cpd' ")
60
+ logger.warning("Using MCSP Auth Type. If this is incorrect please use the '--type' flag to explicitly choose one of 'ibm_iam' or 'mcsp' or 'cpd' ")
61
61
  return self._authenticate(EnvironmentAuthType.MCSP)
62
62
  else:
63
63
  return self._authenticate(self._credentials.auth_type)
@@ -29,12 +29,17 @@ class TempusClient(BaseAPIClient):
29
29
  authenticator=authenticator
30
30
  )
31
31
 
32
+ def get_tempus_endpoint(self) -> str:
33
+ """
34
+ Returns the Tempus endpoint URL
35
+ """
36
+ return self.base_url
32
37
  def create_update_flow_model(self, flow_id: str, model: dict) -> dict:
33
- return self._post(f"/v1/flow-models/{flow_id}", data=model)
38
+ return self._post(f"/flow-models/{flow_id}", data=model)
34
39
 
35
40
  def run_flow(self, flow_id: str, input: dict) -> dict:
36
- return self._post(f"/v1/flows/{flow_id}/versions/TIP/run", data=input)
41
+ return self._post(f"/flows/{flow_id}/versions/TIP/run", data=input)
37
42
 
38
43
  def arun_flow(self, flow_id: str, input: dict) -> dict:
39
- return self._post(f"/v1/flows/{flow_id}/versions/TIP/run/async", data=input)
44
+ return self._post(f"/flows/{flow_id}/versions/TIP/run/async", data=input)
40
45
 
@@ -47,6 +47,16 @@ def is_local_dev(url: str | None = None) -> bool:
47
47
 
48
48
  return False
49
49
 
50
+ def is_ibm_cloud():
51
+ cfg = Config()
52
+ active_env = cfg.read(CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT)
53
+ url = cfg.get(ENVIRONMENTS_SECTION_HEADER, active_env, ENV_WXO_URL_OPT)
54
+
55
+ if url.__contains__("cloud.ibm.com"):
56
+ return True
57
+ return False
58
+
59
+
50
60
  def is_cpd_env(url: str) -> bool:
51
61
  if url.lower().startswith("https://cpd"):
52
62
  return True