ibm-watsonx-orchestrate 1.12.0b0__py3-none-any.whl → 1.13.0b0__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 (66) hide show
  1. ibm_watsonx_orchestrate/__init__.py +2 -1
  2. ibm_watsonx_orchestrate/agent_builder/agents/types.py +5 -5
  3. ibm_watsonx_orchestrate/agent_builder/connections/types.py +34 -3
  4. ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py +11 -2
  5. ibm_watsonx_orchestrate/agent_builder/models/types.py +18 -1
  6. ibm_watsonx_orchestrate/agent_builder/toolkits/base_toolkit.py +1 -1
  7. ibm_watsonx_orchestrate/agent_builder/toolkits/types.py +14 -2
  8. ibm_watsonx_orchestrate/agent_builder/tools/__init__.py +1 -1
  9. ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py +1 -1
  10. ibm_watsonx_orchestrate/agent_builder/tools/langflow_tool.py +61 -1
  11. ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py +6 -0
  12. ibm_watsonx_orchestrate/agent_builder/tools/types.py +21 -3
  13. ibm_watsonx_orchestrate/agent_builder/voice_configurations/__init__.py +1 -1
  14. ibm_watsonx_orchestrate/agent_builder/voice_configurations/types.py +11 -0
  15. ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py +29 -53
  16. ibm_watsonx_orchestrate/cli/commands/connections/connections_command.py +2 -2
  17. ibm_watsonx_orchestrate/cli/commands/connections/connections_controller.py +56 -30
  18. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_command.py +25 -2
  19. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_controller.py +249 -14
  20. ibm_watsonx_orchestrate/cli/commands/copilot/copilot_server_controller.py +4 -4
  21. ibm_watsonx_orchestrate/cli/commands/environment/environment_command.py +5 -1
  22. ibm_watsonx_orchestrate/cli/commands/environment/environment_controller.py +6 -3
  23. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_command.py +3 -2
  24. ibm_watsonx_orchestrate/cli/commands/evaluations/evaluations_controller.py +1 -1
  25. ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py +45 -16
  26. ibm_watsonx_orchestrate/cli/commands/models/model_provider_mapper.py +23 -4
  27. ibm_watsonx_orchestrate/cli/commands/models/models_command.py +2 -2
  28. ibm_watsonx_orchestrate/cli/commands/models/models_controller.py +29 -10
  29. ibm_watsonx_orchestrate/cli/commands/partners/offering/partners_offering_controller.py +21 -4
  30. ibm_watsonx_orchestrate/cli/commands/partners/offering/types.py +7 -15
  31. ibm_watsonx_orchestrate/cli/commands/partners/partners_command.py +1 -1
  32. ibm_watsonx_orchestrate/cli/commands/server/server_command.py +30 -20
  33. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py +2 -2
  34. ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py +139 -27
  35. ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py +2 -2
  36. ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py +79 -36
  37. ibm_watsonx_orchestrate/cli/commands/voice_configurations/voice_configurations_controller.py +23 -11
  38. ibm_watsonx_orchestrate/cli/common.py +26 -0
  39. ibm_watsonx_orchestrate/cli/config.py +33 -2
  40. ibm_watsonx_orchestrate/client/connections/connections_client.py +1 -14
  41. ibm_watsonx_orchestrate/client/copilot/cpe/copilot_cpe_client.py +34 -1
  42. ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py +6 -2
  43. ibm_watsonx_orchestrate/client/model_policies/model_policies_client.py +1 -1
  44. ibm_watsonx_orchestrate/client/models/models_client.py +1 -1
  45. ibm_watsonx_orchestrate/client/threads/threads_client.py +34 -0
  46. ibm_watsonx_orchestrate/client/utils.py +29 -7
  47. ibm_watsonx_orchestrate/docker/compose-lite.yml +58 -8
  48. ibm_watsonx_orchestrate/docker/default.env +26 -17
  49. ibm_watsonx_orchestrate/flow_builder/flows/decorators.py +10 -2
  50. ibm_watsonx_orchestrate/flow_builder/flows/flow.py +90 -16
  51. ibm_watsonx_orchestrate/flow_builder/node.py +14 -2
  52. ibm_watsonx_orchestrate/flow_builder/types.py +57 -3
  53. ibm_watsonx_orchestrate/langflow/__init__.py +0 -0
  54. ibm_watsonx_orchestrate/langflow/langflow_utils.py +195 -0
  55. ibm_watsonx_orchestrate/langflow/lfx_deps.py +84 -0
  56. ibm_watsonx_orchestrate/utils/async_helpers.py +31 -0
  57. ibm_watsonx_orchestrate/utils/docker_utils.py +1177 -33
  58. ibm_watsonx_orchestrate/utils/environment.py +165 -20
  59. ibm_watsonx_orchestrate/utils/exceptions.py +1 -1
  60. ibm_watsonx_orchestrate/utils/tokens.py +51 -0
  61. ibm_watsonx_orchestrate/utils/utils.py +63 -4
  62. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/METADATA +2 -2
  63. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/RECORD +66 -59
  64. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/WHEEL +0 -0
  65. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/entry_points.txt +0 -0
  66. {ibm_watsonx_orchestrate-1.12.0b0.dist-info → ibm_watsonx_orchestrate-1.13.0b0.dist-info}/licenses/LICENSE +0 -0
@@ -6,14 +6,15 @@ import logging
6
6
  import importlib
7
7
  import inspect
8
8
  from pathlib import Path
9
- from typing import List
9
+ from typing import List, Any
10
10
 
11
11
  from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.knowledge_base import KnowledgeBase
12
12
  from ibm_watsonx_orchestrate.client.knowledge_bases.knowledge_base_client import KnowledgeBaseClient
13
13
  from ibm_watsonx_orchestrate.client.base_api_client import ClientAPIException
14
14
  from ibm_watsonx_orchestrate.client.connections import get_connections_client
15
15
  from ibm_watsonx_orchestrate.client.utils import instantiate_client
16
- from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import FileUpload
16
+ from ibm_watsonx_orchestrate.agent_builder.knowledge_bases.types import FileUpload, KnowledgeBaseListEntry
17
+ from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
17
18
 
18
19
  logger = logging.getLogger(__name__)
19
20
 
@@ -198,8 +199,7 @@ class KnowledgeBaseController:
198
199
 
199
200
  logger.info(f"Knowledge base '{kb.name}' updated successfully")
200
201
 
201
-
202
- def knowledge_base_status( self, id: str, name: str) -> None:
202
+ def knowledge_base_status( self, id: str, name: str, format: ListFormats = None) -> dict | str | None:
203
203
  knowledge_base_id = self.get_id(id, name)
204
204
  response = self.get_client().status(knowledge_base_id)
205
205
 
@@ -219,13 +219,25 @@ class KnowledgeBaseController:
219
219
 
220
220
  response["id"] = kbID
221
221
 
222
+ if format == ListFormats.JSON:
223
+ return response
224
+
225
+
222
226
  [table.add_column(to_column_name(col), {}) for col in response.keys()]
223
227
  table.add_row(*[str(val) for val in response.values()])
224
228
 
229
+ if format == ListFormats.Table:
230
+ return rich_table_to_markdown(table)
231
+
225
232
  rich.print(table)
226
233
 
227
234
 
228
- def list_knowledge_bases(self, verbose: bool=False):
235
+ def list_knowledge_bases(self, verbose: bool=False, format: ListFormats=None)-> List[dict[str, Any]] | List[KnowledgeBaseListEntry] | str | None:
236
+
237
+ if verbose and format:
238
+ logger.error("For knowledge base list, `--verbose` and `--format` are mutually exclusive options")
239
+ sys.exit(1)
240
+
229
241
  response = self.get_client().get()
230
242
  knowledge_bases = [KnowledgeBase.model_validate(knowledge_base) for knowledge_base in response]
231
243
 
@@ -234,7 +246,9 @@ class KnowledgeBaseController:
234
246
  for kb in knowledge_bases:
235
247
  knowledge_base_list.append(json.loads(kb.model_dump_json(exclude_none=True)))
236
248
  rich.print(rich.json.JSON(json.dumps(knowledge_base_list, indent=4)))
249
+ return knowledge_base_list
237
250
  else:
251
+ knowledge_base_details=[]
238
252
  table = rich.table.Table(
239
253
  show_header=True,
240
254
  header_style="bold white",
@@ -251,6 +265,11 @@ class KnowledgeBaseController:
251
265
  for column in column_args:
252
266
  table.add_column(column, **column_args[column])
253
267
 
268
+ connections_client = get_connections_client()
269
+ connections = connections_client.list()
270
+
271
+ connections_dict = {conn.connection_id: conn for conn in connections}
272
+
254
273
  for kb in knowledge_bases:
255
274
  app_id = ""
256
275
 
@@ -258,18 +277,28 @@ class KnowledgeBaseController:
258
277
  and kb.conversational_search_tool.index_config is not None \
259
278
  and len(kb.conversational_search_tool.index_config) > 0 \
260
279
  and kb.conversational_search_tool.index_config[0].connection_id is not None:
261
- connections_client = get_connections_client()
262
- app_id = str(connections_client.get_draft_by_id(kb.conversational_search_tool.index_config[0].connection_id))
263
-
264
- table.add_row(
265
- kb.name,
266
- kb.description,
267
- app_id,
268
- str(kb.id)
280
+ conn = connections_dict.get(kb.conversational_search_tool.index_config[0].connection_id)
281
+ if conn:
282
+ app_id = conn.app_id
283
+
284
+ entry = KnowledgeBaseListEntry(
285
+ name=kb.name,
286
+ id=str(kb.id),
287
+ description=kb.description,
288
+ app_id=app_id
269
289
  )
270
-
271
- rich.print(table)
272
-
290
+ if format == ListFormats.JSON:
291
+ knowledge_base_details.append(entry)
292
+ else:
293
+ table.add_row(*entry.get_row_details())
294
+
295
+ match format:
296
+ case ListFormats.JSON:
297
+ return knowledge_base_details
298
+ case ListFormats.Table:
299
+ return rich_table_to_markdown(table)
300
+ case _:
301
+ rich.print(table)
273
302
 
274
303
  def remove_knowledge_base(self, id: str, name: str):
275
304
  knowledge_base_id = self.get_id(id, name)
@@ -101,10 +101,11 @@ PROVIDER_EXTRA_PROPERTIES_LUT = {
101
101
  PROVIDER_REQUIRED_FIELDS = {k:['api_key'] for k in ModelProvider}
102
102
  # Update required fields for each provider
103
103
  # Use sets to denote when a requirement is 'or'
104
+ # Tuples denote combined requirements like 'and'
104
105
  PROVIDER_REQUIRED_FIELDS.update({
105
106
  ModelProvider.WATSONX: PROVIDER_REQUIRED_FIELDS[ModelProvider.WATSONX] + [{'watsonx_space_id', 'watsonx_project_id', 'watsonx_deployment_id'}],
106
107
  ModelProvider.OLLAMA: PROVIDER_REQUIRED_FIELDS[ModelProvider.OLLAMA] + ['custom_host'],
107
- ModelProvider.BEDROCK: [],
108
+ ModelProvider.BEDROCK: [{'api_key', ('aws_secret_access_key', 'aws_access_key_id')}],
108
109
  })
109
110
 
110
111
  # def env_file_to_model_ProviderConfig(model_name: str, env_file_path: str) -> ProviderConfig | None:
@@ -158,16 +159,34 @@ def _validate_extra_fields(provider: ModelProvider, cfg: ProviderConfig) -> None
158
159
  if cfg.__dict__.get(attr) is not None and attr not in accepted_fields:
159
160
  logger.warning(f"The config option '{attr}' is not used by provider '{provider}'")
160
161
 
162
+ def _check_credential_provided(cred: str | tuple, provided_creds: set) -> bool:
163
+ if isinstance(cred, tuple):
164
+ return all(item in provided_creds for item in cred)
165
+ else:
166
+ return cred in provided_creds
167
+
168
+ def _format_missing_credential(missing_credential: set) -> str:
169
+ parts = []
170
+ for cred in missing_credential:
171
+ if isinstance(cred, tuple):
172
+ formatted = " and ".join(f"{x}" for x in cred)
173
+ parts.append(f"({formatted})")
174
+ else:
175
+ parts.append(f"{cred}")
176
+
177
+ return " or ".join(parts)
178
+
179
+
161
180
  def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id: str = None) -> None:
162
181
  provided_credentials = set([k for k,v in dict(cfg).items() if v is not None])
163
182
  required_creds = PROVIDER_REQUIRED_FIELDS[provider]
164
183
  missing_credentials = []
165
184
  for cred in required_creds:
166
185
  if isinstance(cred, set):
167
- if not any(c in provided_credentials for c in cred):
186
+ if not any(_check_credential_provided(c, provided_credentials) for c in cred):
168
187
  missing_credentials.append(cred)
169
188
  else:
170
- if cred not in provided_credentials:
189
+ if not _check_credential_provided(cred, provided_credentials):
171
190
  missing_credentials.append(cred)
172
191
 
173
192
  if len(missing_credentials) > 0:
@@ -177,7 +196,7 @@ def _validate_requirements(provider: ModelProvider, cfg: ProviderConfig, app_id:
177
196
  missing_credentials_string = f"Be sure to include the following required fields for provider '{provider}' in the connection '{app_id}':"
178
197
  for cred in missing_credentials:
179
198
  if isinstance(cred, set):
180
- cred_str = ' or '.join(list(cred))
199
+ cred_str = _format_missing_credential(cred)
181
200
  else:
182
201
  cred_str = cred
183
202
  missing_credentials_string += f"\n\t - {cred_str}"
@@ -136,11 +136,11 @@ def models_policy_import(
136
136
  def models_policy_add(
137
137
  name: Annotated[
138
138
  str,
139
- typer.Option("--name", "-n", help="The name of the model to remove"),
139
+ typer.Option("--name", "-n", help="The name of the model policy to add"),
140
140
  ],
141
141
  models: Annotated[
142
142
  List[str],
143
- typer.Option('--model', '-m', help='The name of the model to add'),
143
+ typer.Option('--model', '-m', help='A list of model names the policy should contain'),
144
144
  ],
145
145
  strategy: Annotated[
146
146
  ModelPolicyStrategyMode,
@@ -6,7 +6,7 @@ import yaml
6
6
  import importlib
7
7
  import inspect
8
8
  from pathlib import Path
9
- from typing import List
9
+ from typing import List, Optional
10
10
 
11
11
  import requests
12
12
  import rich
@@ -16,10 +16,11 @@ from ibm_watsonx_orchestrate.client.model_policies.model_policies_client import
16
16
  from ibm_watsonx_orchestrate.agent_builder.model_policies.types import ModelPolicy, ModelPolicyInner, \
17
17
  ModelPolicyRetry, ModelPolicyStrategy, ModelPolicyStrategyMode, ModelPolicyTarget
18
18
  from ibm_watsonx_orchestrate.client.models.models_client import ModelsClient
19
- from ibm_watsonx_orchestrate.agent_builder.models.types import VirtualModel, ProviderConfig, ModelType, ANTHROPIC_DEFAULT_MAX_TOKENS
19
+ from ibm_watsonx_orchestrate.agent_builder.models.types import VirtualModel, ProviderConfig, ModelType, ANTHROPIC_DEFAULT_MAX_TOKENS, ModelListEntry
20
20
  from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_cpd_env
21
21
  from ibm_watsonx_orchestrate.client.connections import get_connection_id, ConnectionType
22
22
  from ibm_watsonx_orchestrate.utils.environment import EnvService
23
+ from ibm_watsonx_orchestrate.cli.common import ListFormats, rich_table_to_markdown
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
 
@@ -149,7 +150,7 @@ class ModelsController:
149
150
  self.model_policies_client = instantiate_client(ModelPoliciesClient)
150
151
  return self.model_policies_client
151
152
 
152
- def list_models(self, print_raw: bool = False) -> None:
153
+ def list_models(self, print_raw: bool = False, format: Optional[ListFormats] = None) -> List[ModelListEntry] | str |None:
153
154
  models_client: ModelsClient = self.get_models_client()
154
155
  model_policies_client: ModelPoliciesClient = self.get_model_policies_client()
155
156
  global WATSONX_URL
@@ -224,6 +225,7 @@ class ModelsController:
224
225
 
225
226
  console.print("[yellow]★[/yellow] [italic dim]indicates a supported and preferred model[/italic dim]\n[blue dim]✨️[/blue dim] [italic dim]indicates a model from a custom provider[/italic dim]" )
226
227
  else:
228
+ model_details = []
227
229
  table = rich.table.Table(
228
230
  show_header=True,
229
231
  title="[bold]Available Models[/bold]",
@@ -234,15 +236,32 @@ class ModelsController:
234
236
  table.add_column(col)
235
237
 
236
238
  for model in (virtual_models + virtual_model_policies):
237
- table.add_row(f"✨️ {model.name}", model.description or 'No description provided.')
239
+ entry = ModelListEntry(
240
+ name=model.name,
241
+ description=model.description,
242
+ is_custom=True
243
+ )
244
+ model_details.append(entry)
245
+ table.add_row(*entry.get_row_details())
238
246
 
239
247
  for model in sorted_models:
240
- model_id = model.get("model_id", "N/A")
241
- short_desc = model.get("short_description", "No description provided.")
242
- marker = "★ " if any(pref in model_id.lower() for pref in preferred_list) else ""
243
- table.add_row(f"[yellow]{marker}[/yellow]watsonx/{model_id}", short_desc)
244
-
245
- rich.print(table)
248
+ name = model.get("model_id", "N/A")
249
+ entry = ModelListEntry(
250
+ name=name,
251
+ description=model.get("short_description"),
252
+ is_custom=False,
253
+ recommended=any(pref in name.lower() for pref in preferred_list)
254
+ )
255
+ model_details.append(entry)
256
+ table.add_row(*entry.get_row_details())
257
+
258
+ match format:
259
+ case ListFormats.JSON:
260
+ return model_details
261
+ case ListFormats.Table:
262
+ return rich_table_to_markdown(table)
263
+ case _:
264
+ rich.print(table)
246
265
 
247
266
  def import_model(self, file: str, app_id: str | None) -> List[VirtualModel]:
248
267
  from ibm_watsonx_orchestrate.cli.commands.models.model_provider_mapper import validate_ProviderConfig # lazily import this because the lut building is expensive
@@ -23,6 +23,7 @@ from ibm_watsonx_orchestrate.client.connections import get_connections_client
23
23
  from ibm_watsonx_orchestrate.agent_builder.connections.types import ConnectionEnvironment
24
24
  from ibm_watsonx_orchestrate.cli.commands.connections.connections_controller import export_connection
25
25
  from ibm_watsonx_orchestrate.cli.commands.tools.tools_controller import ToolsController
26
+ from ibm_watsonx_orchestrate.utils.utils import sanitize_catalog_label
26
27
  from .types import *
27
28
 
28
29
  APPLICATIONS_FILE_VERSION = '1.16.0'
@@ -54,7 +55,7 @@ def get_tool_bindings(tool_names: list[str]) -> dict[str, dict]:
54
55
 
55
56
  return results
56
57
 
57
- def _patch_agent_yamls(project_root: Path, publisher_name: str):
58
+ def _patch_agent_yamls(project_root: Path, publisher_name: str, parent_agent_name: str):
58
59
  agents_dir = project_root / "agents"
59
60
  if not agents_dir.exists():
60
61
  return
@@ -70,15 +71,18 @@ def _patch_agent_yamls(project_root: Path, publisher_name: str):
70
71
  if "language_support" not in agent_data:
71
72
  agent_data["language_support"] = ["English"]
72
73
  if "icon" not in agent_data:
73
- agent_data["icon"] = "inline-svg-of-icon"
74
+ agent_data["icon"] = AGENT_CATALOG_ONLY_PLACEHOLDERS['icon']
74
75
  if "category" not in agent_data:
75
76
  agent_data["category"] = "agent"
76
77
  if "supported_apps" not in agent_data:
77
78
  agent_data["supported_apps"] = []
79
+ if "agent_role" not in agent_data:
80
+ agent_data["agent_role"] = "manager" if agent_data.get("name") == parent_agent_name else "collaborator"
78
81
 
79
82
  with open(agent_yaml, "w") as f:
80
83
  yaml.safe_dump(agent_data, f, sort_keys=False)
81
84
 
85
+
82
86
  def _create_applications_entry(connection_config: dict) -> dict:
83
87
  return {
84
88
  'app_id': connection_config.get('app_id'),
@@ -116,6 +120,15 @@ class PartnersOfferingController:
116
120
  sys.exit(1)
117
121
 
118
122
  def create(self, offering: str, publisher_name: str, agent_type: str, agent_name: str):
123
+
124
+ # Sanitize offering name
125
+ original_offering = offering
126
+ offering = sanitize_catalog_label(offering)
127
+
128
+ if offering != original_offering:
129
+ logger.warning("Offering name must contain only alpahnumeric characters or underscore")
130
+ logger.info(f"Offering '{original_offering}' has been updated to '{offering}'")
131
+
119
132
  # Create parent project folder
120
133
  project_root = self.root / offering
121
134
 
@@ -179,7 +192,7 @@ class PartnersOfferingController:
179
192
  output_zip.unlink(missing_ok=True)
180
193
 
181
194
  # Patch the agent yamls with publisher, tags, icon, etc.
182
- _patch_agent_yamls(project_root, publisher_name)
195
+ _patch_agent_yamls(project_root=project_root, publisher_name=publisher_name, parent_agent_name=agent_name)
183
196
 
184
197
 
185
198
  # Create offering.yaml file -------------------------------------------------------
@@ -337,12 +350,16 @@ class PartnersOfferingController:
337
350
  **agent_data
338
351
  )
339
352
  agent = Agent.model_validate(agent_details)
340
- AgentsController().persist_record(agent=agent)
341
353
  case AgentKind.EXTERNAL:
342
354
  agent_details = parse_create_external_args(
343
355
  **agent_data
344
356
  )
345
357
  agent = ExternalAgent.model_validate(agent_details)
358
+
359
+ # Placeholder detection
360
+ for label,placeholder in AGENT_CATALOG_ONLY_PLACEHOLDERS.items():
361
+ if agent_data.get(label) == placeholder:
362
+ logger.warning(f"Placeholder '{label}' detected for agent '{agent_name}', please ensure '{label}' is correct before packaging.")
346
363
 
347
364
  agent_json_path = f"{top_level_folder}/agents/{agent_name}/config.json"
348
365
  zf.writestr(agent_json_path, json.dumps(agent_data, indent=2))
@@ -24,6 +24,10 @@ CATALOG_ONLY_FIELDS = [
24
24
  'supported_apps'
25
25
  ]
26
26
 
27
+ AGENT_CATALOG_ONLY_PLACEHOLDERS = {
28
+ 'icon': "inline-svg-of-icon",
29
+ }
30
+
27
31
  class AgentKind(str, Enum):
28
32
  NATIVE = "native"
29
33
  EXTERNAL = "external"
@@ -87,21 +91,9 @@ class Offering(BaseModel):
87
91
  return values
88
92
 
89
93
  def validate_ready_for_packaging(self):
90
- self.test_for_placeholder_values()
91
-
92
- def test_for_placeholder_values(self):
93
- placholders = False
94
- # part numbers
95
- if not self.part_number:
96
- raise ValueError(f"Offering '{self.name}' does not have valid part numbers")
97
-
98
- for (k,v) in self.part_number.model_dump().items():
99
- if v == CATALOG_PLACEHOLDERS['part_number']:
100
- logger.warning(f"Placeholder part number detected for platform '{k}', please ensure valid part numbers are entered before packaging.")
101
- placholders = True
102
-
103
- if placholders:
104
- raise ValueError(f"Offering '{self.name}' cannot be packaged with placeholder values")
94
+ # Leaving this fn here in case we want to reintroduce validation
95
+ pass
96
+
105
97
 
106
98
 
107
99
 
@@ -3,7 +3,7 @@ import typer
3
3
  from ibm_watsonx_orchestrate.cli.commands.partners import partners_controller
4
4
  from ibm_watsonx_orchestrate.cli.commands.partners.offering.partners_offering_command import partners_offering
5
5
 
6
- partners_app = typer.Typer(no_args_is_help=True, hidden=True)
6
+ partners_app = typer.Typer(no_args_is_help=True)
7
7
 
8
8
  partners_app.add_typer(
9
9
  partners_offering,
@@ -16,7 +16,7 @@ from ibm_watsonx_orchestrate.client.utils import instantiate_client
16
16
  from ibm_watsonx_orchestrate.cli.commands.environment.environment_controller import _login
17
17
 
18
18
  from ibm_watsonx_orchestrate.cli.config import PROTECTED_ENV_NAME, clear_protected_env_credentials_token, Config, \
19
- AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER, USER_ENV_CACHE_HEADER, LICENSE_HEADER, \
19
+ AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER, LICENSE_HEADER, \
20
20
  ENV_ACCEPT_LICENSE
21
21
  from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
22
22
  from ibm_watsonx_orchestrate.utils.docker_utils import DockerLoginService, DockerComposeCore, DockerUtils
@@ -38,8 +38,12 @@ def refresh_local_credentials() -> None:
38
38
  """
39
39
  Refresh the local credentials
40
40
  """
41
- clear_protected_env_credentials_token()
42
- _login(name=PROTECTED_ENV_NAME, apikey=None)
41
+ try:
42
+ clear_protected_env_credentials_token()
43
+ _login(name=PROTECTED_ENV_NAME, apikey=None)
44
+
45
+ except:
46
+ logger.warning("Failed to refresh local credentials, please run `orchestrate env activate local`")
43
47
 
44
48
  def run_compose_lite(
45
49
  final_env_file: Path,
@@ -48,13 +52,14 @@ def run_compose_lite(
48
52
  experimental_with_ibm_telemetry=False,
49
53
  with_doc_processing=False,
50
54
  with_voice=False,
55
+ with_connections_ui=False,
51
56
  with_langflow=False,
52
57
  ) -> None:
53
- EnvService.prepare_clean_env(final_env_file)
54
- db_tag = EnvService.read_env_file(final_env_file).get('DBTAG', None)
58
+ env_service.prepare_clean_env(final_env_file)
59
+ db_tag = env_service.read_env_file(final_env_file).get('DBTAG', None)
55
60
  logger.info(f"Detected architecture: {platform.machine()}, using DBTAG: {db_tag}")
56
61
 
57
- compose_core = DockerComposeCore(env_service)
62
+ compose_core = DockerComposeCore(env_service=env_service)
58
63
 
59
64
  # Step 1: Start only the DB container
60
65
  result = compose_core.service_up(service_name="wxo-server-db", friendly_name="WxO Server DB", final_env_file=final_env_file, compose_env=os.environ)
@@ -80,6 +85,8 @@ def run_compose_lite(
80
85
  profiles.append("docproc")
81
86
  if with_voice:
82
87
  profiles.append("voice")
88
+ if with_connections_ui:
89
+ profiles.append("connections-ui")
83
90
  if with_langflow:
84
91
  profiles.append("langflow")
85
92
 
@@ -194,7 +201,7 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
194
201
  logger.error("Healthcheck failed orchestrate server. Make sure you start the server components with `orchestrate server start` before trying to start the chat UI")
195
202
  return False
196
203
 
197
- compose_core = DockerComposeCore(env_service)
204
+ compose_core = DockerComposeCore(env_service=env_service)
198
205
 
199
206
  result = compose_core.service_up(service_name="ui", friendly_name="UI", final_env_file=final_env_file)
200
207
 
@@ -231,7 +238,7 @@ def run_compose_lite_down_ui(user_env_file: Path, is_reset: bool = False) -> Non
231
238
 
232
239
  cli_config = Config()
233
240
  env_service = EnvService(cli_config)
234
- compose_core = DockerComposeCore(env_service)
241
+ compose_core = DockerComposeCore(env_service=env_service)
235
242
 
236
243
  result = compose_core.service_down(service_name="ui", friendly_name="UI", final_env_file=final_env_file, is_reset=is_reset)
237
244
 
@@ -252,7 +259,7 @@ def run_compose_lite_down(final_env_file: Path, is_reset: bool = False) -> None:
252
259
 
253
260
  cli_config = Config()
254
261
  env_service = EnvService(cli_config)
255
- compose_core = DockerComposeCore(env_service)
262
+ compose_core = DockerComposeCore(env_service=env_service)
256
263
 
257
264
  result = compose_core.services_down(final_env_file=final_env_file, is_reset=is_reset)
258
265
 
@@ -273,7 +280,7 @@ def run_compose_lite_logs(final_env_file: Path) -> None:
273
280
 
274
281
  cli_config = Config()
275
282
  env_service = EnvService(cli_config)
276
- compose_core = DockerComposeCore(env_service)
283
+ compose_core = DockerComposeCore(env_service=env_service)
277
284
 
278
285
  result = compose_core.services_logs(final_env_file=final_env_file, should_follow=True)
279
286
 
@@ -358,11 +365,14 @@ def server_start(
358
365
  '--with-voice', '-v',
359
366
  help='Enable voice controller to interact with the chat via voice channels'
360
367
  ),
368
+ with_connections_ui: bool = typer.Option(
369
+ False,
370
+ '--with-connections-ui', '-c',
371
+ help='Enables connections ui to facilitate OAuth connections and credential management via a UI'),
361
372
  with_langflow: bool = typer.Option(
362
373
  False,
363
374
  '--with-langflow',
364
- help='Enable Langflow UI, available at http://localhost:7861',
365
- hidden=True
375
+ help='Enable Langflow UI, available at http://localhost:7861'
366
376
  ),
367
377
  ):
368
378
  cli_config = Config()
@@ -389,7 +399,8 @@ def server_start(
389
399
  env_service.set_compose_file_path_in_env(custom_compose_file)
390
400
 
391
401
  user_env = env_service.get_user_env(user_env_file=user_env_file, fallback_to_persisted_env=False)
392
- env_service.persist_user_env(user_env, include_secrets=persist_env_secrets)
402
+ developer_edition_source = env_service.get_dev_edition_source_core(user_env)
403
+ env_service.persist_user_env(user_env, include_secrets=persist_env_secrets, source=developer_edition_source)
393
404
 
394
405
  merged_env_dict = env_service.prepare_server_env_vars(user_env=user_env, should_drop_auth_routes=False)
395
406
 
@@ -425,6 +436,7 @@ def server_start(
425
436
  experimental_with_ibm_telemetry=experimental_with_ibm_telemetry,
426
437
  with_doc_processing=with_doc_processing,
427
438
  with_voice=with_voice,
439
+ with_connections_ui=with_connections_ui,
428
440
  with_langflow=with_langflow, env_service=env_service)
429
441
 
430
442
  run_db_migration()
@@ -444,10 +456,7 @@ def server_start(
444
456
  )
445
457
  exit(1)
446
458
 
447
- try:
448
- refresh_local_credentials()
449
- except:
450
- logger.warning("Failed to refresh local credentials, please run `orchestrate env activate local`")
459
+ refresh_local_credentials()
451
460
 
452
461
  logger.info(f"You can run `orchestrate env activate local` to set your environment or `orchestrate chat start` to start the UI service and begin chatting.")
453
462
 
@@ -455,9 +464,10 @@ def server_start(
455
464
  logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
456
465
  if with_doc_processing:
457
466
  logger.info(f"Document processing in Flows (Public Preview) has been enabled.")
467
+ if with_connections_ui:
468
+ logger.info("Connections UI can be found at http://localhost:3412/connectors")
458
469
  if with_langflow:
459
470
  logger.info("Langflow has been enabled, the Langflow UI is available at http://localhost:7861")
460
-
461
471
  @server_app.command(name="stop")
462
472
  def server_stop(
463
473
  user_env_file: str = typer.Option(
@@ -562,7 +572,7 @@ def run_db_migration() -> None:
562
572
 
563
573
  cli_config = Config()
564
574
  env_service = EnvService(cli_config)
565
- compose_core = DockerComposeCore(env_service)
575
+ compose_core = DockerComposeCore(env_service=env_service)
566
576
 
567
577
  result = compose_core.service_container_bash_exec(service_name="wxo-server-db",
568
578
  log_message="Running Database Migration...",
@@ -616,7 +626,7 @@ def create_langflow_db() -> None:
616
626
 
617
627
  cli_config = Config()
618
628
  env_service = EnvService(cli_config)
619
- compose_core = DockerComposeCore(env_service)
629
+ compose_core = DockerComposeCore(env_service=env_service)
620
630
 
621
631
  result = compose_core.service_container_bash_exec(service_name="wxo-server-db",
622
632
  log_message="Preparing Langflow resources...",
@@ -47,11 +47,11 @@ def import_toolkit(
47
47
  ] = None,
48
48
  url: Annotated[
49
49
  Optional[str],
50
- typer.Option("--url", "-u", help="The URL of the remote MCP server", hidden=True),
50
+ typer.Option("--url", "-u", help="The URL of the remote MCP server"),
51
51
  ] = None,
52
52
  transport: Annotated[
53
53
  ToolkitTransportKind,
54
- typer.Option("--transport", help="The communication protocol to use for the remote MCP server. Only \"sse\" or \"streamable_http\" supported", hidden=True),
54
+ typer.Option("--transport", help="The communication protocol to use for the remote MCP server. Only \"sse\" or \"streamable_http\" supported"),
55
55
  ] = None,
56
56
  tools: Annotated[
57
57
  Optional[str],