ibm-watsonx-orchestrate 1.0.1__py3-none-any.whl → 1.2.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.
@@ -5,7 +5,8 @@
5
5
 
6
6
  pkg_name = "ibm-watsonx-orchestrate"
7
7
 
8
- __version__ = "1.0.1"
8
+ __version__ = "1.2.0"
9
+
9
10
 
10
11
 
11
12
  from ibm_watsonx_orchestrate.utils.logging.logger import setup_logging
@@ -121,6 +121,7 @@ def tool(
121
121
  _desc = description
122
122
  if description is None and doc is not None:
123
123
  _desc = doc.description
124
+
124
125
 
125
126
  spec = ToolSpec(
126
127
  name=name or fn.__name__,
@@ -150,7 +151,11 @@ def tool(
150
151
 
151
152
  sig = inspect.signature(fn)
152
153
  if not input_schema:
153
- input_schema_model: type[BaseModel] = create_schema_from_function(spec.name, fn, parse_docstring=True)
154
+ try:
155
+ input_schema_model: type[BaseModel] = create_schema_from_function(spec.name, fn, parse_docstring=True)
156
+ except:
157
+ logger.warning("Unable to properly parse parameter descriptions due to incorrectly formatted docstring. This may result in degraded agent performance. To fix this, please ensure the docstring conforms to Google's docstring format.")
158
+ input_schema_model: type[BaseModel] = create_schema_from_function(spec.name, fn, parse_docstring=False)
154
159
  input_schema_json = input_schema_model.model_json_schema()
155
160
  input_schema_json = dereference_refs(input_schema_json)
156
161
 
@@ -165,7 +170,7 @@ def tool(
165
170
  )
166
171
  else:
167
172
  spec.input_schema = input_schema
168
-
173
+
169
174
  _validate_input_schema(spec.input_schema)
170
175
 
171
176
  if not output_schema:
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import List, Any, Dict, Literal, Optional
2
+ from typing import List, Any, Dict, Literal, Optional, Union
3
3
 
4
4
  from pydantic import BaseModel, model_validator, ConfigDict, Field, AliasChoices
5
5
 
@@ -14,7 +14,7 @@ class ToolPermission(str, Enum):
14
14
  class JsonSchemaObject(BaseModel):
15
15
  model_config = ConfigDict(extra='allow')
16
16
 
17
- type: Optional[Literal['object', 'string', 'number', 'integer', 'boolean', 'array', 'null']] = None
17
+ type: Optional[Union[Literal['object', 'string', 'number', 'integer', 'boolean', 'array', 'null'], List[Literal['object', 'string', 'number', 'integer', 'boolean', 'array', 'null']]]] = None
18
18
  title: str | None = None
19
19
  description: str | None = None
20
20
  properties: Optional[Dict[str, 'JsonSchemaObject']] = None
@@ -34,13 +34,19 @@ class JsonSchemaObject(BaseModel):
34
34
  aliasName: str | None = None
35
35
  "Runtime feature where the sdk can provide the original name of a field before prefixing"
36
36
 
37
+ @model_validator(mode='after')
38
+ def normalize_type_field(self) -> 'JsonSchemaObject':
39
+ if isinstance(self.type, list):
40
+ self.type = self.type[0]
41
+ return self
42
+
37
43
 
38
44
  class ToolRequestBody(BaseModel):
39
45
  model_config = ConfigDict(extra='allow')
40
46
 
41
47
  type: Literal['object']
42
48
  properties: Dict[str, JsonSchemaObject]
43
- required: List[str]
49
+ required: Optional[List[str]] = None
44
50
 
45
51
 
46
52
  class ToolResponseBody(BaseModel):
@@ -52,7 +58,7 @@ class ToolResponseBody(BaseModel):
52
58
  items: JsonSchemaObject = None
53
59
  uniqueItems: bool = None
54
60
  anyOf: List['JsonSchemaObject'] = None
55
- required: List[str] = None
61
+ required: Optional[List[str]] = None
56
62
 
57
63
  class OpenApiSecurityScheme(BaseModel):
58
64
  type: Literal['apiKey', 'http', 'oauth2', 'openIdConnect']
@@ -128,6 +134,10 @@ class SkillToolBinding(BaseModel):
128
134
  class ClientSideToolBinding(BaseModel):
129
135
  pass
130
136
 
137
+ class McpToolBinding(BaseModel):
138
+ server_url: Optional[str] = None
139
+ source: str
140
+ connections: Dict[str, str]
131
141
 
132
142
  class ToolBinding(BaseModel):
133
143
  openapi: OpenApiToolBinding = None
@@ -135,6 +145,7 @@ class ToolBinding(BaseModel):
135
145
  wxflows: WxFlowsToolBinding = None
136
146
  skill: SkillToolBinding = None
137
147
  client_side: ClientSideToolBinding = None
148
+ mcp: McpToolBinding = None
138
149
 
139
150
  @model_validator(mode='after')
140
151
  def validate_binding_type(self) -> 'ToolBinding':
@@ -143,7 +154,8 @@ class ToolBinding(BaseModel):
143
154
  self.python is not None,
144
155
  self.wxflows is not None,
145
156
  self.skill is not None,
146
- self.client_side is not None
157
+ self.client_side is not None,
158
+ self.mcp is not None
147
159
  ]
148
160
  if sum(bindings) == 0:
149
161
  raise ValueError("One binding must be set")
@@ -159,4 +171,5 @@ class ToolSpec(BaseModel):
159
171
  input_schema: ToolRequestBody = None
160
172
  output_schema: ToolResponseBody = None
161
173
  binding: ToolBinding = None
174
+ toolkit_id: str | None = None
162
175
 
@@ -1,30 +1,25 @@
1
+ import importlib.resources as resources
1
2
  import logging
2
- import sys
3
+ import os
4
+ import platform
3
5
  import subprocess
6
+ import sys
4
7
  import tempfile
5
- from pathlib import Path
6
- import requests
7
8
  import time
8
- import os
9
- import platform
10
-
9
+ from pathlib import Path
11
10
 
12
- import typer
13
- import importlib.resources as resources
14
11
  import jwt
12
+ import requests
13
+ import typer
14
+ from dotenv import dotenv_values
15
15
 
16
- from dotenv import dotenv_values, load_dotenv
17
-
18
- from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
19
- from ibm_watsonx_orchestrate.client.analytics.llm.analytics_llm_client import AnalyticsLLMClient, AnalyticsLLMConfig, \
20
- AnalyticsLLMUpsertToolIdentifier
21
16
  from ibm_watsonx_orchestrate.client.utils import instantiate_client, check_token_validity, is_local_dev
22
-
23
- from ibm_watsonx_orchestrate.cli.commands.environment.environment_controller import _login, _decode_token
17
+ from ibm_watsonx_orchestrate.cli.commands.environment.environment_controller import _login
18
+ from ibm_watsonx_orchestrate.cli.config import LICENSE_HEADER, \
19
+ ENV_ACCEPT_LICENSE
24
20
  from ibm_watsonx_orchestrate.cli.config import PROTECTED_ENV_NAME, clear_protected_env_credentials_token, Config, \
25
- AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, ENVIRONMENTS_SECTION_HEADER, ENV_WXO_URL_OPT, \
26
- CONTEXT_SECTION_HEADER, CONTEXT_ACTIVE_ENV_OPT, AUTH_SECTION_HEADER
27
- from dotenv import dotenv_values, load_dotenv
21
+ AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE, AUTH_MCSP_TOKEN_OPT, AUTH_SECTION_HEADER, USER_ENV_CACHE_HEADER
22
+ from ibm_watsonx_orchestrate.client.agents.agent_client import AgentClient
28
23
 
29
24
  logger = logging.getLogger(__name__)
30
25
 
@@ -52,11 +47,11 @@ def ensure_docker_compose_installed() -> list:
52
47
  typer.echo("Unable to find an installed docker-compose or docker compose")
53
48
  sys.exit(1)
54
49
 
55
- def docker_login(iam_api_key: str, registry_url: str) -> None:
50
+ def docker_login(api_key: str, registry_url: str, username:str = "iamapikey") -> None:
56
51
  logger.info(f"Logging into Docker registry: {registry_url} ...")
57
52
  result = subprocess.run(
58
- ["docker", "login", "-u", "iamapikey", "--password-stdin", registry_url],
59
- input=iam_api_key.encode("utf-8"),
53
+ ["docker", "login", "-u", username, "--password-stdin", registry_url],
54
+ input=api_key.encode("utf-8"),
60
55
  capture_output=True,
61
56
  )
62
57
  if result.returncode != 0:
@@ -64,6 +59,24 @@ def docker_login(iam_api_key: str, registry_url: str) -> None:
64
59
  sys.exit(1)
65
60
  logger.info("Successfully logged in to Docker.")
66
61
 
62
+ def docker_login_by_dev_edition_source(env_dict: dict, source: str) -> None:
63
+ registry_url = env_dict["REGISTRY_URL"]
64
+ if source == "internal":
65
+ iam_api_key = env_dict.get("DOCKER_IAM_KEY")
66
+ if not iam_api_key:
67
+ raise ValueError("DOCKER_IAM_KEY is required in the environment file if WO_DEVELOPER_EDITION_SOURCE is set to 'internal'.")
68
+ docker_login(iam_api_key, registry_url, "iamapikey")
69
+ elif source == "myibm":
70
+ wo_entitlement_key = env_dict.get("WO_ENTITLEMENT_KEY")
71
+ if not wo_entitlement_key:
72
+ raise ValueError("WO_ENTITLEMENT_KEY is required in the environment file.")
73
+ docker_login(wo_entitlement_key, registry_url, "cp")
74
+ elif source == "orchestrate":
75
+ wo_auth_type = env_dict.get("WO_AUTH_TYPE")
76
+ if not wo_auth_type:
77
+ raise ValueError("WO_AUTH_TYPE is required in the environment file if WO_DEVELOPER_EDITION_SOURCE is set to 'orchestrate'.")
78
+ api_key, username = get_docker_cred_by_wo_auth_type(env_dict, wo_auth_type)
79
+ docker_login(api_key, registry_url, username)
67
80
 
68
81
  def get_compose_file() -> Path:
69
82
  with resources.as_file(
@@ -93,9 +106,51 @@ def merge_env(
93
106
  user_env = dotenv_values(str(user_env_path))
94
107
  merged.update(user_env)
95
108
 
96
-
97
109
  return merged
98
110
 
111
+ def get_default_registry_env_vars_by_dev_edition_source(env_dict: dict, source: str) -> dict[str,str]:
112
+ component_registry_var_names = {key for key in env_dict if key.endswith("_REGISTRY")}
113
+
114
+ result = {}
115
+ if source == "internal":
116
+ result["REGISTRY_URL"] = "us.icr.io"
117
+ for name in component_registry_var_names:
118
+ result[name] = "us.icr.io/watson-orchestrate-private"
119
+ elif source == "myibm":
120
+ result["REGISTRY_URL"] = "cp.icr.io"
121
+ for name in component_registry_var_names:
122
+ result[name] = "cp.icr.io/cp/wxo-lite"
123
+ elif source == "orchestrate":
124
+ raise NotImplementedError("The 'orchestrate' source is not implemented yet.")
125
+ # TODO: confirm with Tej about the registry url for orchestrate source
126
+ return result
127
+
128
+ def get_dev_edition_source(env_dict: dict) -> str:
129
+ source = env_dict.get("WO_DEVELOPER_EDITION_SOURCE")
130
+
131
+ if source:
132
+ return source
133
+ if env_dict.get("WO_INSTANCE"):
134
+ return "orchestrate"
135
+ return "myibm"
136
+
137
+ def get_docker_cred_by_wo_auth_type(env_dict: dict, auth_type: str) -> tuple[str, str]:
138
+ if auth_type in {"mcsp", "ibm_iam"}:
139
+ wo_api_key = env_dict.get("WO_API_KEY")
140
+ if not wo_api_key:
141
+ raise ValueError("WO_API_KEY is required in the environment file if the WO_AUTH_TYPE is set to 'mcsp' or 'ibm_iam'.")
142
+ return wo_api_key, "wouser"
143
+ elif auth_type == "cpd":
144
+ wo_api_key = env_dict.get("WO_API_KEY")
145
+ wo_password = env_dict.get("WO_PASSWORD")
146
+ if not wo_api_key and not wo_password:
147
+ raise ValueError("WO_API_KEY or WO_PASSWORD is required in the environment file if the WO_AUTH_TYPE is set to 'cpd'.")
148
+ wo_username = env_dict.get("WO_USERNAME")
149
+ if not wo_username:
150
+ raise ValueError("WO_USERNAME is required in the environment file if the WO_AUTH_TYPE is set to 'cpd'.")
151
+ return wo_api_key or wo_password, wo_username # type: ignore[return-value]
152
+ else:
153
+ raise ValueError(f"Unknown value for WO_AUTH_TYPE: {auth_type}. Must be one of ['mcsp', 'ibm_iam', 'cpd'].")
99
154
 
100
155
  def apply_llm_api_key_defaults(env_dict: dict) -> None:
101
156
  llm_value = env_dict.get("WATSONX_APIKEY")
@@ -138,7 +193,30 @@ def refresh_local_credentials() -> None:
138
193
  clear_protected_env_credentials_token()
139
194
  _login(name=PROTECTED_ENV_NAME, apikey=None)
140
195
 
196
+ NON_SECRET_ENV_ITEMS = {
197
+ "WO_DEVELOPER_EDITION_SOURCE",
198
+ "WO_INSTANCE",
199
+ "USE_SAAS_ML_TOOLS_RUNTIME",
200
+ "WXO_MCSP_EXCHANGE_URL",
201
+ "OPENSOURCE_REGISTRY_PROXY"
202
+ }
203
+ def persist_user_env(env: dict, include_secrets: bool = False) -> None:
204
+ if include_secrets:
205
+ persistable_env = env
206
+ else:
207
+ persistable_env = {k:env[k] for k in NON_SECRET_ENV_ITEMS if k in env}
141
208
 
209
+ cfg = Config()
210
+ cfg.save(
211
+ {
212
+ USER_ENV_CACHE_HEADER: persistable_env
213
+ }
214
+ )
215
+
216
+ def get_persisted_user_env() -> dict | None:
217
+ cfg = Config()
218
+ user_env = cfg.get(USER_ENV_CACHE_HEADER) if cfg.get(USER_ENV_CACHE_HEADER) else None
219
+ return user_env
142
220
 
143
221
  def run_compose_lite(final_env_file: Path, experimental_with_langfuse=False, with_flow_runtime=False) -> None:
144
222
  compose_path = get_compose_file()
@@ -256,12 +334,20 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
256
334
  compose_path = get_compose_file()
257
335
  compose_command = ensure_docker_compose_installed()
258
336
  ensure_docker_installed()
259
- default_env_path = get_default_env_file()
260
- logger.debug(f"user env file: {user_env_file}")
261
- merged_env_dict = merge_env(
262
- default_env_path,
263
- user_env_file if user_env_file else None
264
- )
337
+
338
+ default_env = read_env_file(get_default_env_file())
339
+ user_env = read_env_file(user_env_file) if user_env_file else {}
340
+ if not user_env:
341
+ user_env = get_persisted_user_env()
342
+
343
+ dev_edition_source = get_dev_edition_source(user_env)
344
+ default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, source=dev_edition_source)
345
+
346
+ merged_env_dict = {
347
+ **default_env,
348
+ **default_registry_vars,
349
+ **user_env,
350
+ }
265
351
 
266
352
  _login(name=PROTECTED_ENV_NAME)
267
353
  auth_cfg = Config(AUTH_CONFIG_FILE_FOLDER, AUTH_CONFIG_FILE)
@@ -271,21 +357,17 @@ def run_compose_lite_ui(user_env_file: Path) -> bool:
271
357
  tenant_id = token.get('woTenantId', None)
272
358
  merged_env_dict['REACT_APP_TENANT_ID'] = tenant_id
273
359
 
274
-
275
- registry_url = merged_env_dict.get("REGISTRY_URL")
276
- if not registry_url:
277
- logger.error("Error: REGISTRY_URL is required in the environment file.")
278
- sys.exit(1)
279
-
280
360
  agent_client = instantiate_client(AgentClient)
281
361
  agents = agent_client.get()
282
362
  if not agents:
283
363
  logger.error("No agents found for the current environment. Please create an agent before starting the chat.")
284
364
  sys.exit(1)
285
365
 
286
- iam_api_key = merged_env_dict.get("DOCKER_IAM_KEY")
287
- if iam_api_key:
288
- docker_login(iam_api_key, registry_url)
366
+ try:
367
+ docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
368
+ except ValueError as ignored:
369
+ # do nothing, as the docker login here is not mandatory
370
+ pass
289
371
 
290
372
  #These are to removed warning and not used in UI component
291
373
  if not 'WATSONX_SPACE_ID' in merged_env_dict:
@@ -436,6 +518,35 @@ def run_compose_lite_logs(final_env_file: Path, is_reset: bool = False) -> None:
436
518
  )
437
519
  sys.exit(1)
438
520
 
521
+ def confirm_accepts_license_agreement(accepts_by_argument: bool):
522
+ cfg = Config()
523
+ accepts_license = cfg.read(LICENSE_HEADER, ENV_ACCEPT_LICENSE)
524
+ if accepts_license != True:
525
+ logger.warning(('''
526
+ By running the following command your machine will install IBM watsonx Orchestrate Developer Edition, which is governed by the following IBM license agreement:
527
+ - * https://www.ibm.com/support/customer/csol/terms/?id=L-YRMZ-PB6MHM&lc=en
528
+ Additionally, the following prerequisite open source programs will be obtained from Docker Hub and will be installed on your machine. Each of the below programs are Separately Licensed Code, and are governed by the separate license agreements identified below, and not by the IBM license agreement:
529
+ * redis (7.2) - https://github.com/redis/redis/blob/7.2.7/COPYING
530
+ * minio - https://github.com/minio/minio/blob/master/LICENSE
531
+ * milvus-io - https://github.com/milvus-io/milvus/blob/master/LICENSE
532
+ * etcd - https://github.com/etcd-io/etcd/blob/main/LICENSE
533
+ * clickhouse-server - https://github.com/ClickHouse/ClickHouse/blob/master/LICENSE
534
+ * langfuse - https://github.com/langfuse/langfuse/blob/main/LICENSE
535
+ After installation, you are solely responsible for obtaining and installing updates and fixes, including security patches, for the above prerequisite open source programs. To update images the customer will run `orchestrate server reset && orchestrate server start -e .env`.
536
+ ''').strip())
537
+ if not accepts_by_argument:
538
+ result = input('\nTo accept the terms and conditions of the IBM license agreement and the Separately Licensed Code licenses above please enter "I accept": ')
539
+ else:
540
+ result = None
541
+ if result == 'I accept' or accepts_by_argument:
542
+ cfg.write(LICENSE_HEADER, ENV_ACCEPT_LICENSE, True)
543
+ else:
544
+ logger.error('The terms and conditions were not accepted, exiting.')
545
+ exit(1)
546
+
547
+
548
+
549
+
439
550
  @server_app.command(name="start")
440
551
  def server_start(
441
552
  user_env_file: str = typer.Option(
@@ -454,33 +565,46 @@ def server_start(
454
565
  help='Option to start server with tempus-runtime.',
455
566
  hidden=True
456
567
  )
568
+ ,
569
+ persist_env_secrets: bool = typer.Option(
570
+ False,
571
+ '--persist-env-secrets', '-p',
572
+ help='Option to store secret values from the provided env file in the config file (~/.config/orchestrate/config.yaml)',
573
+ hidden=True
574
+ ),
575
+ accept_terms_and_conditions: bool = typer.Option(
576
+ False,
577
+ "--accept-terms-and-conditions",
578
+ help="By providing this flag you accept the terms and conditions outlined in the logs on server start."
579
+ ),
457
580
  ):
581
+ confirm_accepts_license_agreement(accept_terms_and_conditions)
582
+
458
583
  if user_env_file and not Path(user_env_file).exists():
459
584
  logger.error(f"Error: The specified environment file '{user_env_file}' does not exist.")
460
585
  sys.exit(1)
461
586
  ensure_docker_installed()
462
587
 
463
- default_env_path = get_default_env_file()
588
+ default_env = read_env_file(get_default_env_file())
589
+ user_env = read_env_file(user_env_file) if user_env_file else {}
590
+ persist_user_env(user_env, include_secrets=persist_env_secrets)
591
+ dev_edition_source = get_dev_edition_source(user_env)
592
+ default_registry_vars = get_default_registry_env_vars_by_dev_edition_source(default_env, source=dev_edition_source)
464
593
 
465
- merged_env_dict = merge_env(
466
- default_env_path,
467
- Path(user_env_file) if user_env_file else None
468
- )
594
+ merged_env_dict = {
595
+ **default_env,
596
+ **default_registry_vars,
597
+ **user_env,
598
+ }
469
599
 
470
600
  merged_env_dict['DBTAG'] = get_dbtag_from_architecture(merged_env_dict=merged_env_dict)
471
601
 
472
- iam_api_key = merged_env_dict.get("DOCKER_IAM_KEY")
473
- if not iam_api_key:
474
- logger.error("Error: DOCKER_IAM_KEY is required in the environment file.")
475
- sys.exit(1)
476
-
477
- registry_url = merged_env_dict.get("REGISTRY_URL")
478
- if not registry_url:
479
- logger.error("Error: REGISTRY_URL is required in the environment file.")
602
+ try:
603
+ docker_login_by_dev_edition_source(merged_env_dict, dev_edition_source)
604
+ except ValueError as e:
605
+ logger.error(f"Error: {e}")
480
606
  sys.exit(1)
481
607
 
482
- docker_login(iam_api_key, registry_url)
483
-
484
608
  apply_llm_api_key_defaults(merged_env_dict)
485
609
 
486
610
 
@@ -511,6 +635,9 @@ def server_start(
511
635
 
512
636
  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.")
513
637
 
638
+ if experimental_with_langfuse:
639
+ logger.info(f"You can access the observability platform Langfuse at http://localhost:3010, username: orchestrate@ibm.com, password: orchestrate")
640
+
514
641
  if with_flow_runtime:
515
642
  logger.info(f"Starting with flow runtime")
516
643
 
@@ -0,0 +1,71 @@
1
+ import typer
2
+ from typing import List
3
+ from typing_extensions import Annotated, Optional
4
+ from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_controller import ToolkitController, ToolkitKind
5
+
6
+ toolkits_app = typer.Typer(no_args_is_help=True)
7
+
8
+ @toolkits_app.command(name="import")
9
+ def import_toolkit(
10
+ kind: Annotated[
11
+ ToolkitKind,
12
+ typer.Option("--kind", "-k", help="Kind of toolkit, currently only MCP is supported"),
13
+ ],
14
+ name: Annotated[
15
+ str,
16
+ typer.Option("--name", "-n", help="Name of the toolkit"),
17
+ ],
18
+ description: Annotated[
19
+ str,
20
+ typer.Option("--description", help="Description of the toolkit"),
21
+ ],
22
+ package_root: Annotated[
23
+ str,
24
+ typer.Option("--package-root", "-p", help="Root directory of the MCP server package"),
25
+ ],
26
+ command: Annotated[
27
+ str,
28
+ typer.Option(
29
+ "--command",
30
+ help="Command to start the MCP server. Can be a string (e.g. 'node dist/index.js --transport stdio') "
31
+ "or a JSON-style list of arguments (e.g. '[\"node\", \"dist/index.js\", \"--transport\", \"stdio\"]'). "
32
+ "The first argument will be used as the executable, the rest as its arguments."
33
+ ),
34
+ ],
35
+ tools: Annotated[
36
+ Optional[str],
37
+ typer.Option("--tools", "-t", help="Comma-separated list of tools to import. Or you can use `*` to use all tools"),
38
+ ] = None,
39
+ app_id: Annotated[
40
+ List[str],
41
+ typer.Option(
42
+ "--app-id", "-a",
43
+ help='The app id of the connection to associate with this tool. A application connection represents the server authentication credentials needed to connect to this tool. Only type key_value is currently supported for MCP.'
44
+ )
45
+ ] = None
46
+ ):
47
+ if tools == "*":
48
+ tool_list = ["*"] # Wildcard to use all tools
49
+ elif tools:
50
+ tool_list = [tool.strip() for tool in tools.split(",")]
51
+ else:
52
+ tool_list = None
53
+
54
+ toolkit_controller = ToolkitController(
55
+ kind=kind,
56
+ name=name,
57
+ description=description,
58
+ package_root=package_root,
59
+ command=command,
60
+ )
61
+ toolkit_controller.import_toolkit(tools=tool_list, app_id=app_id)
62
+
63
+ @toolkits_app.command(name="remove")
64
+ def remove_toolkit(
65
+ name: Annotated[
66
+ str,
67
+ typer.Option("--name", "-n", help="Name of the toolkit you wish to remove"),
68
+ ],
69
+ ):
70
+ toolkit_controller = ToolkitController()
71
+ toolkit_controller.remove_toolkit(name=name)
@@ -0,0 +1,212 @@
1
+ import os
2
+ import zipfile
3
+ import tempfile
4
+ from typing import List, Optional
5
+ from enum import Enum
6
+ import logging
7
+ import sys
8
+ import re
9
+ import requests
10
+ from ibm_watsonx_orchestrate.client.toolkit.toolkit_client import ToolKitClient
11
+ from ibm_watsonx_orchestrate.client.utils import instantiate_client
12
+ from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
13
+ from ibm_watsonx_orchestrate.client.connections import get_connections_client
14
+ import typer
15
+ import json
16
+ from rich.console import Console
17
+ from rich.progress import Progress, SpinnerColumn, TextColumn
18
+ from ibm_watsonx_orchestrate.client.utils import is_local_dev
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ class ToolkitKind(str, Enum):
23
+ MCP = "mcp"
24
+
25
+ def get_connection_id(app_id: str) -> str:
26
+ connections_client = get_connections_client()
27
+ existing_draft_configuration = connections_client.get_config(app_id=app_id, env='draft')
28
+ existing_live_configuration = connections_client.get_config(app_id=app_id, env='live')
29
+
30
+ for config in [existing_draft_configuration, existing_live_configuration]:
31
+ if config and config.security_scheme != 'key_value_creds':
32
+ logger.error("Only key_value credentials are currently supported")
33
+ exit(1)
34
+ connection_id = None
35
+ if app_id is not None:
36
+ connection = connections_client.get(app_id=app_id)
37
+ if not connection:
38
+ logger.error(f"No connection exists with the app-id '{app_id}'")
39
+ exit(1)
40
+ connection_id = connection.connection_id
41
+ return connection_id
42
+
43
+ def validate_params(kind: str):
44
+ if kind != ToolkitKind.MCP:
45
+ raise ValueError(f"Unsupported toolkit kind: {kind}")
46
+
47
+
48
+ class ToolkitController:
49
+ def __init__(
50
+ self,
51
+ kind: ToolkitKind = None,
52
+ name: str = None,
53
+ description: str = None,
54
+ package_root: str = None,
55
+ command: str = None,
56
+ ):
57
+ self.kind = kind
58
+ self.name = name
59
+ self.description = description
60
+ self.package_root = package_root
61
+ self.command = command
62
+ self.client = None
63
+
64
+ def get_client(self) -> ToolKitClient:
65
+ if not self.client:
66
+ self.client = instantiate_client(ToolKitClient)
67
+ return self.client
68
+
69
+ def import_toolkit(self, tools: Optional[List[str]] = None, app_id: Optional[List[str]] = None):
70
+ if not is_local_dev():
71
+ logger.error("This functionality is only available for Local Environments")
72
+ sys.exit(1)
73
+
74
+ if app_id and isinstance(app_id, str):
75
+ app_id = [app_id]
76
+ elif not app_id:
77
+ app_id = []
78
+
79
+ validate_params(kind=self.kind)
80
+
81
+ remapped_connections = self._remap_connections(app_id)
82
+
83
+ client = self.get_client()
84
+ draft_toolkits = client.get_draft_by_name(toolkit_name=self.name)
85
+ if len(draft_toolkits) > 0:
86
+ logger.error(f"Existing toolkit found with name '{self.name}'. Failed to create toolkit.")
87
+ sys.exit(1)
88
+
89
+ with tempfile.TemporaryDirectory() as tmpdir:
90
+ # Handle zip file or directory
91
+ if self.package_root.endswith(".zip") and os.path.isfile(self.package_root):
92
+ zip_file_path = self.package_root
93
+ else:
94
+ zip_file_path = os.path.join(tmpdir, os.path.basename(f"{self.package_root.rstrip(os.sep)}.zip"))
95
+ with zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED) as mcp_zip_tool_artifacts:
96
+ self._populate_zip(self.package_root, mcp_zip_tool_artifacts)
97
+
98
+ try:
99
+ command_parts = json.loads(self.command)
100
+ if not isinstance(command_parts, list):
101
+ raise ValueError("JSON command must be a list of strings")
102
+ except (json.JSONDecodeError, ValueError):
103
+ command_parts = self.command.split()
104
+
105
+ command = command_parts[0]
106
+ args = command_parts[1:]
107
+
108
+ console = Console()
109
+ # List tools if not provided
110
+ if tools is None:
111
+ with Progress(
112
+ SpinnerColumn(spinner_name="dots"),
113
+ TextColumn("[progress.description]{task.description}"),
114
+ transient=True,
115
+ console=console,
116
+ ) as progress:
117
+ progress.add_task(description="No tools specified, retrieving all tools from provided MCP server", total=None)
118
+ tools = self.get_client().list_tools(
119
+ zip_file_path=zip_file_path,
120
+ command=command,
121
+ args=args,
122
+ )
123
+ # Normalize tools to a list of tool names
124
+ tools = [
125
+ tool["name"] if isinstance(tool, dict) and "name" in tool else tool
126
+ for tool in tools
127
+ ]
128
+
129
+
130
+ logger.info("✅ The following tools will be imported:")
131
+ for tool in tools:
132
+ console.print(f" • {tool}")
133
+
134
+
135
+ # Create toolkit metadata
136
+ payload = {
137
+ "name": self.name,
138
+ "description": self.description,
139
+ "mcp": {
140
+ "source": "files",
141
+ "command": command,
142
+ "args": args,
143
+ "tools": tools,
144
+ "connections": remapped_connections,
145
+ }
146
+ }
147
+ toolkit = self.get_client().create_toolkit(payload)
148
+ toolkit_id = toolkit["id"]
149
+
150
+ console = Console()
151
+ # Upload zip file
152
+ with Progress(
153
+ SpinnerColumn(spinner_name="dots"),
154
+ TextColumn("[progress.description]{task.description}"),
155
+ transient=True,
156
+ console=console,
157
+ ) as progress:
158
+ progress.add_task(description="Uploading toolkit zip file...", total=None)
159
+ self.get_client().upload(toolkit_id=toolkit_id, zip_file_path=zip_file_path)
160
+ logger.info(f"Successfully imported tool kit {self.name}")
161
+
162
+ def _populate_zip(self, package_root: str, zipfile: zipfile.ZipFile) -> str:
163
+ for root, _, files in os.walk(package_root):
164
+ for file in files:
165
+ full_path = os.path.join(root, file)
166
+ relative_path = os.path.relpath(full_path, start=package_root)
167
+ zipfile.write(full_path, arcname=relative_path)
168
+ return zipfile
169
+
170
+ def _remap_connections(self, app_ids: List[str]):
171
+ app_id_dict = {}
172
+ for app_id in app_ids:
173
+ split_pattern = re.compile(r"(?<!\\)=")
174
+ split_id = re.split(split_pattern, app_id)
175
+ split_id = [x.replace("\\=", "=") for x in split_id]
176
+ if len(split_id) == 2:
177
+ runtime_id, local_id = split_id
178
+ elif len(split_id) == 1:
179
+ runtime_id = split_id[0]
180
+ local_id = split_id[0]
181
+ else:
182
+ raise typer.BadParameter(f"The provided --app-id '{app_id}' is not valid. This is likely caused by having mutliple equal signs, please use '\\=' to represent a literal '=' character")
183
+
184
+ if not len(runtime_id.strip()) or not len(local_id.strip()):
185
+ raise typer.BadParameter(f"The provided --app-id '{app_id}' is not valid. --app-id cannot be empty or whitespace")
186
+
187
+ runtime_id = sanatize_app_id(runtime_id)
188
+ app_id_dict[runtime_id] = get_connection_id(local_id)
189
+
190
+ return app_id_dict
191
+
192
+
193
+ def remove_toolkit(self, name: str):
194
+ if not is_local_dev():
195
+ logger.error("This functionality is only available for Local Environments")
196
+ sys.exit(1)
197
+ try:
198
+ client = self.get_client()
199
+ draft_toolkits = client.get_draft_by_name(toolkit_name=name)
200
+ if len(draft_toolkits) > 1:
201
+ logger.error(f"Multiple existing toolkits found with name '{name}'. Failed to remove toolkit")
202
+ sys.exit(1)
203
+ if len(draft_toolkits) > 0:
204
+ draft_toolkit = draft_toolkits[0]
205
+ toolkit_id = draft_toolkit.get("id")
206
+ self.get_client().delete(toolkit_id=toolkit_id)
207
+ logger.info(f"Successfully removed tool {name}")
208
+ else:
209
+ logger.warning(f"No toolkit named '{name}' found")
210
+ except requests.HTTPError as e:
211
+ logger.error(e.response.text)
212
+ exit(1)
@@ -27,8 +27,9 @@ from ibm_watsonx_orchestrate.cli.config import Config, CONTEXT_SECTION_HEADER, C
27
27
  DEFAULT_CONFIG_FILE_CONTENT
28
28
  from ibm_watsonx_orchestrate.agent_builder.connections import ConnectionSecurityScheme, ExpectedCredentials
29
29
  from ibm_watsonx_orchestrate.client.tools.tool_client import ToolClient
30
+ from ibm_watsonx_orchestrate.client.toolkit.toolkit_client import ToolKitClient
30
31
  from ibm_watsonx_orchestrate.client.connections import get_connections_client, get_connection_type
31
- from ibm_watsonx_orchestrate.client.utils import instantiate_client
32
+ from ibm_watsonx_orchestrate.client.utils import instantiate_client, is_local_dev
32
33
  from ibm_watsonx_orchestrate.utils.utils import sanatize_app_id
33
34
 
34
35
  from ibm_watsonx_orchestrate import __version__
@@ -41,6 +42,7 @@ __supported_characters_pattern = re.compile("^(\\w|_)+$")
41
42
  class ToolKind(str, Enum):
42
43
  openapi = "openapi"
43
44
  python = "python"
45
+ mcp = "mcp"
44
46
  # skill = "skill"
45
47
 
46
48
  def validate_app_ids(kind: ToolKind, **args) -> None:
@@ -275,14 +277,8 @@ def import_python_tool(file: str, requirements_file: str = None, app_id: List[st
275
277
  resolved_requirements_file = get_resolved_py_tool_reqs_file(tool_file=file, requirements_file=requirements_file,
276
278
  package_root=resolved_package_root)
277
279
 
278
- if resolved_requirements_file is None:
279
- logger.warning(f"No requirements file.")
280
-
281
- if resolved_requirements_file != requirements_file:
282
- logger.info(f"Resolved Requirements file: \"{resolved_requirements_file}\"")
283
-
284
- else:
285
- logger.info(f"Requirements file: \"{requirements_file}\"")
280
+ if resolved_requirements_file is not None:
281
+ logger.info(f"Using requirement file: \"{resolved_requirements_file}\"")
286
282
 
287
283
  if resolved_requirements_file is not None:
288
284
  try:
@@ -374,21 +370,24 @@ class ToolsController:
374
370
  response = self.get_client().get()
375
371
  tool_specs = [ToolSpec.model_validate(tool) for tool in response]
376
372
  tools = [BaseTool(spec=spec) for spec in tool_specs]
377
-
378
373
 
379
374
  if verbose:
380
375
  tools_list = []
381
376
  for tool in tools:
382
-
383
377
  tools_list.append(json.loads(tool.dumps_spec()))
384
378
 
385
379
  rich.print(JSON(json.dumps(tools_list, indent=4)))
386
380
  else:
387
381
  table = rich.table.Table(show_header=True, header_style="bold white", show_lines=True)
388
- columns = ["Name", "Description", "Permission", "Type", "App ID"]
382
+ columns = ["Name", "Description", "Permission", "Type", "Toolkit", "App ID"]
389
383
  for column in columns:
390
384
  table.add_column(column)
391
-
385
+
386
+ connections_client = get_connections_client()
387
+ connections = connections_client.list()
388
+
389
+ connections_dict = {conn.connection_id: conn for conn in connections}
390
+
392
391
  for tool in tools:
393
392
  tool_binding = tool.__tool_spec__.binding
394
393
 
@@ -400,25 +399,49 @@ class ToolsController:
400
399
  elif tool_binding.python is not None and hasattr(tool_binding.python, "connections") and tool_binding.python.connections is not None:
401
400
  for conn in tool_binding.python.connections:
402
401
  connection_ids.append(tool_binding.python.connections[conn])
403
-
404
- connections_client = get_connections_client()
402
+ elif tool_binding.mcp is not None and hasattr(tool_binding.mcp, "connections"):
403
+ for conn in tool_binding.mcp.connections:
404
+ connection_ids.append(tool_binding.mcp.connections[conn])
405
+
406
+
405
407
  app_ids = []
406
408
  for connection_id in connection_ids:
407
- app_id = str(connections_client.get_draft_by_id(connection_id))
409
+ connection = connections_dict.get(connection_id)
410
+ if connection:
411
+ app_id = str(connection.app_id or connection.connection_id)
412
+ elif connection_id:
413
+ app_id = str(connection_id)
414
+ else:
415
+ app_id = ""
408
416
  app_ids.append(app_id)
409
417
 
410
418
  if tool_binding.python is not None:
411
419
  tool_type=ToolKind.python
412
420
  elif tool_binding.openapi is not None:
413
421
  tool_type=ToolKind.openapi
422
+ elif tool_binding.mcp is not None:
423
+ tool_type=ToolKind.mcp
414
424
  else:
415
425
  tool_type="Unknown"
416
426
 
427
+ toolkit_name = ""
428
+
429
+ if is_local_dev():
430
+ toolkit_client = instantiate_client(ToolKitClient)
431
+ if tool.__tool_spec__.toolkit_id:
432
+ toolkit = toolkit_client.get_draft_by_id(tool.__tool_spec__.toolkit_id)
433
+ if isinstance(toolkit, dict) and "name" in toolkit:
434
+ toolkit_name = toolkit["name"]
435
+ elif toolkit:
436
+ toolkit_name = str(toolkit)
437
+
438
+
417
439
  table.add_row(
418
440
  tool.__tool_spec__.name,
419
441
  tool.__tool_spec__.description,
420
442
  tool.__tool_spec__.permission,
421
443
  tool_type,
444
+ toolkit_name,
422
445
  ", ".join(app_ids),
423
446
  )
424
447
 
@@ -472,7 +495,7 @@ class ToolsController:
472
495
  raise typer.BadParameter(f"Symbolic links in packages are not supported. - {path_str}")
473
496
 
474
497
  try:
475
- zip_tool_artifacts.write(path_str, arcname=path_str[len(str(Path(resolved_package_root))) + 1:])
498
+ zip_tool_artifacts.write(path_str, arcname=str(Path(path_str).relative_to(Path(resolved_package_root))))
476
499
 
477
500
  except Exception as ex:
478
501
  logger.error(f"Could not write file {path_str} to artifact. {ex}")
@@ -12,6 +12,8 @@ AUTH_SECTION_HEADER = "auth"
12
12
  CONTEXT_SECTION_HEADER = "context"
13
13
  ENVIRONMENTS_SECTION_HEADER = "environments"
14
14
  PYTHON_REGISTRY_HEADER = "python_registry"
15
+ USER_ENV_CACHE_HEADER = "cached_user_env"
16
+ LICENSE_HEADER = "license"
15
17
 
16
18
  # Option Names
17
19
  AUTH_MCSP_API_KEY_OPT = "wxo_mcsp_api_key"
@@ -24,6 +26,7 @@ ENV_WXO_URL_OPT = "wxo_url"
24
26
  ENV_IAM_URL_OPT = "iam_url"
25
27
  PROTECTED_ENV_NAME = "local"
26
28
  ENV_AUTH_TYPE = "auth_type"
29
+ ENV_ACCEPT_LICENSE = 'accepts_license_agreements'
27
30
 
28
31
  DEFAULT_LOCAL_SERVICE_URL = "http://localhost:4321"
29
32
  CHAT_UI_PORT = "3000"
@@ -40,7 +43,8 @@ DEFAULT_CONFIG_FILE_CONTENT = {
40
43
  PROTECTED_ENV_NAME: {
41
44
  ENV_WXO_URL_OPT: DEFAULT_LOCAL_SERVICE_URL
42
45
  }
43
- }
46
+ },
47
+ USER_ENV_CACHE_HEADER: {}
44
48
  }
45
49
 
46
50
  AUTH_CONFIG_FILE_FOLDER = f"{os.path.expanduser('~')}/.cache/orchestrate"
@@ -11,6 +11,7 @@ from ibm_watsonx_orchestrate.cli.commands.models.models_command import models_ap
11
11
  from ibm_watsonx_orchestrate.cli.commands.environment.environment_command import environment_app
12
12
  from ibm_watsonx_orchestrate.cli.commands.channels.channels_command import channel_app
13
13
  from ibm_watsonx_orchestrate.cli.commands.knowledge_bases.knowledge_bases_command import knowledge_bases_app
14
+ from ibm_watsonx_orchestrate.cli.commands.toolkit.toolkit_command import toolkits_app
14
15
 
15
16
  app = typer.Typer(
16
17
  no_args_is_help=True,
@@ -18,15 +19,16 @@ app = typer.Typer(
18
19
  )
19
20
  app.add_typer(login_app)
20
21
  app.add_typer(environment_app, name="env", help='Add, remove, or select the activate env other commands will interact with (either your local server or a production instance)')
21
- app.add_typer(tools_app, name="tools", help='Interact with the tools in your active env')
22
22
  app.add_typer(agents_app, name="agents", help='Interact with the agents in your active env')
23
+ app.add_typer(tools_app, name="tools", help='Interact with the tools in your active env')
24
+ app.add_typer(toolkits_app, name="toolkits", help="Interact with the toolkits in your active env")
25
+ app.add_typer(knowledge_bases_app, name="knowledge-bases", help="Upload knowledge your agents can search through to your active env")
23
26
  app.add_typer(connections_app, name="connections", help='Interact with the agents in your active env')
24
- app.add_typer(server_app, name="server", help='Manipulate your local wxo lite server [requires docker pull credentials]')
25
- app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local list wxo lite server [requires docker pull credentials]')
27
+ app.add_typer(server_app, name="server", help='Manipulate your local Orchestrate Developer Edition server [requires entitlement]')
28
+ app.add_typer(chat_app, name="chat", help='Launch the chat ui for your local Developer Edition server [requires entitlement]')
26
29
  app.add_typer(models_app, name="models", help='List the available large language models (llms) that can be used in your agent definitions')
30
+ app.add_typer(channel_app, name="channels", help="Configure channels where your agent can exist on (such as embedded webchat)")
27
31
  app.add_typer(settings_app, name="settings", help='Configure the settings for your active env')
28
- app.add_typer(channel_app, name="channels")
29
- app.add_typer(knowledge_bases_app, name="knowledge-bases")
30
32
 
31
33
  if __name__ == "__main__":
32
34
  app()
@@ -0,0 +1,81 @@
1
+ from ibm_watsonx_orchestrate.client.base_api_client import BaseAPIClient, ClientAPIException
2
+ from typing_extensions import List
3
+ import os
4
+ import json
5
+
6
+ class ToolKitClient(BaseAPIClient):
7
+ # POST /toolkits/prepare/list-tools
8
+ def list_tools(self, zip_file_path: str, command: str, args: List[str]) -> List[str]:
9
+ """
10
+ List the available tools inside the MCP server
11
+ """
12
+
13
+ filename = os.path.basename(zip_file_path)
14
+
15
+ list_toolkit_obj = {
16
+ "source": "files",
17
+ "command": command,
18
+ "args": args,
19
+ }
20
+
21
+ with open(zip_file_path, "rb") as f:
22
+ files = {
23
+ "list_toolkit_obj": (None, json.dumps(list_toolkit_obj), "application/json"),
24
+ "file": (filename, f, "application/zip"),
25
+ }
26
+
27
+ response = self._post("/orchestrate/toolkits/prepare/list-tools", files=files)
28
+
29
+ return response.get("tools", [])
30
+
31
+
32
+ # POST /api/v1/orchestrate/toolkits
33
+ def create_toolkit(self, payload) -> dict:
34
+ """
35
+ Creates new toolkit metadata
36
+ """
37
+ try:
38
+ return self._post("/orchestrate/toolkits", data=payload)
39
+ except ClientAPIException as e:
40
+ if e.response.status_code == 400 and "already exists" in e.response.text:
41
+ raise ClientAPIException(
42
+ status_code=400,
43
+ message=f"There is already a Toolkit with the same name that exists for this tenant."
44
+ )
45
+ raise(e)
46
+
47
+ # POST /toolkits/{toolkit-id}/upload
48
+ def upload(self, toolkit_id: str, zip_file_path: str) -> dict:
49
+ """
50
+ Upload zip file to the toolkit.
51
+ """
52
+ filename = os.path.basename(zip_file_path)
53
+ with open(zip_file_path, "rb") as f:
54
+ files = {
55
+ "file": (filename, f, "application/zip", {"Expires": "0"})
56
+ }
57
+ return self._post(f"/orchestrate/toolkits/{toolkit_id}/upload", files=files)
58
+
59
+ # DELETE /toolkits/{toolkit-id}
60
+ def delete(self, toolkit_id: str) -> dict:
61
+ return self._delete(f"/orchestrate/toolkits/{toolkit_id}")
62
+
63
+ def get_draft_by_name(self, toolkit_name: str) -> List[dict]:
64
+ return self.get_drafts_by_names([toolkit_name])
65
+
66
+ def get_drafts_by_names(self, toolkit_names: List[str]) -> List[dict]:
67
+ formatted_toolkit_names = [f"names={x}" for x in toolkit_names]
68
+ return self._get(f"/orchestrate/toolkits?{'&'.join(formatted_toolkit_names)}")
69
+
70
+ def get_draft_by_id(self, toolkit_id: str) -> dict:
71
+ if toolkit_id is None:
72
+ return ""
73
+ else:
74
+ try:
75
+ toolkit = self._get(f"/orchestrate/toolkits/{toolkit_id}")
76
+ return toolkit
77
+ except ClientAPIException as e:
78
+ if e.response.status_code == 404 and "not found with the given name" in e.response.text:
79
+ return ""
80
+ raise(e)
81
+
@@ -13,7 +13,7 @@ services:
13
13
  command: redis-server --loglevel warning
14
14
 
15
15
  wxo-server-db:
16
- image: us.icr.io/${DB_REGISTRY:-watson-orchestrate-private}/wxo-server-db:${DBTAG:-latest}
16
+ image: ${DB_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-server-db:${DBTAG:-latest}
17
17
  restart: unless-stopped
18
18
  environment:
19
19
  POSTGRES_USER: ${POSTGRES_USER:-postgres}
@@ -32,7 +32,7 @@ services:
32
32
  command: -c shared_preload_libraries=pgsodium
33
33
 
34
34
  wxo-server-connection-manager:
35
- image: us.icr.io/${CM_REGISTRY:-watson-orchestrate-private}/wxo-connections:${CM_TAG:-latest}
35
+ image: ${CM_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-connections:${CM_TAG:-latest}
36
36
  platform: linux/amd64
37
37
  restart: unless-stopped
38
38
  environment:
@@ -44,12 +44,13 @@ services:
44
44
  DB_PORT: ${DB_PORT:-5432}
45
45
  DB_ENCRYPTION_KEY: abc
46
46
  WXO_SERVER_URL: http://wxo-server:4321
47
- MAX_POOL: 10
47
+ MAX_POOL: 60
48
+ DEPLOYMENT_MODE: laptop
48
49
  ports:
49
50
  - 3001:3001
50
51
 
51
52
  ui:
52
- image: us.icr.io/${UI_REGISTRY:-watson-orchestrate-private}/wxo-chat:${UITAG:-latest}
53
+ image: ${UI_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-chat:${UITAG:-latest}
53
54
  platform: linux/amd64
54
55
  restart: unless-stopped
55
56
  environment:
@@ -74,6 +75,7 @@ services:
74
75
  MAX_FILE_UPLOAD_SIZE_BYTES_EXCEL: 1048576
75
76
  VCAP_APP_HOST: localhost
76
77
  DEPLOYMENT_PLATFORM: laptop
78
+ CONNECTION_MANAGER_URL: http://localhost:3001
77
79
  DISMISS_NOTIFICATION_TIMEOUT: 10000
78
80
  STANDALONE: "true"
79
81
  STREAM_TIMEOUT: ${STREAM_TIMEOUT:-120000}
@@ -87,7 +89,7 @@ services:
87
89
  - ui-data:/data
88
90
 
89
91
  wxo-builder:
90
- image: us.icr.io/${BUILDER_REGISTRY:-watson-orchestrate-private}/wxo-builder:${BUILDER_TAG:-latest}
92
+ image: ${BUILDER_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-builder:${BUILDER_TAG:-latest}
91
93
  platform: linux/amd64
92
94
  restart: unless-stopped
93
95
  environment:
@@ -199,7 +201,7 @@ services:
199
201
  - wxo-server-redis
200
202
 
201
203
  wxo-server:
202
- image: us.icr.io/${SERVER_REGISTRY:-watson-orchestrate-private}/wxo-server-server:${SERVER_TAG:-latest}
204
+ image: ${SERVER_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-server-server:${SERVER_TAG:-latest}
203
205
  platform: linux/amd64
204
206
  restart: unless-stopped
205
207
  ports:
@@ -294,7 +296,7 @@ services:
294
296
  MILVUS_PASSWORD: Milvus
295
297
 
296
298
  wxo-server-worker:
297
- image: us.icr.io/${WORKER_REGISTRY:-watson-orchestrate-private}/wxo-server-conversation_controller:${WORKER_TAG:-latest}
299
+ image: ${WORKER_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-server-conversation_controller:${WORKER_TAG:-latest}
298
300
  platform: linux/amd64
299
301
  restart: unless-stopped
300
302
  depends_on:
@@ -372,7 +374,7 @@ services:
372
374
  MILVUS_PASSWORD: Milvus
373
375
 
374
376
  tools-runtime-manager:
375
- image: us.icr.io/${TRM_REGISTRY:-watson-orchestrate-private}/tools-runtime-manager:${TRM_TAG:-latest}
377
+ image: ${TRM_REGISTRY:-us.icr.io/watson-orchestrate-private}/tools-runtime-manager:${TRM_TAG:-latest}
376
378
  platform: linux/amd64
377
379
  restart: unless-stopped
378
380
  ports:
@@ -409,7 +411,7 @@ services:
409
411
  - "host.docker.internal:host-gateway"
410
412
 
411
413
  tools-runtime:
412
- image: us.icr.io/${TR_REGISTRY:-watson-orchestrate-private}/tools-runtime:${TR_TAG:-latest}
414
+ image: ${TR_REGISTRY:-us.icr.io/watson-orchestrate-private}/tools-runtime:${TR_TAG:-latest}
413
415
  platform: linux/amd64
414
416
  ports:
415
417
  - "8000:8000"
@@ -527,7 +529,7 @@ services:
527
529
 
528
530
 
529
531
  wxo-tempus-runtime:
530
- image: us.icr.io/watson-orchestrate-private/wxo-tempus-runtime:${FLOW_RUNTIME_TAG:-latest}
532
+ image: ${FLOW_RUMTIME_REGISTRY:-us.icr.io/watson-orchestrate-private}/wxo-tempus-runtime:${FLOW_RUNTIME_TAG:-latest}
531
533
  restart: unless-stopped
532
534
  platform: linux/amd64
533
535
  profiles: [with-tempus-runtime]
@@ -593,3 +595,4 @@ volumes:
593
595
  networks:
594
596
  default:
595
597
  name: wxo-server
598
+
@@ -1,4 +1,3 @@
1
- REGISTRY_URL=us.icr.io
2
1
  #DOCKER_IAM_KEY=dummy #Must Define in env
3
2
  #You can generate any JWT_SECRET with python -c 'import secrets; print(secrets.token_hex(32))'
4
3
  JWT_SECRET=11759cbc89dbec64956715e10a854eb38f8b7a1775bdf68142786170f5e8b5b2
@@ -47,35 +46,39 @@ CELERY_RESULTS_TTL="3600"
47
46
  EVENT_BROKER_TTL="-1"
48
47
 
49
48
  # START -- IMAGE REGISTRIES AND TAGS
50
- SERVER_TAG=29-04-2025
51
- SERVER_REGISTRY=watson-orchestrate-private
49
+ # THE VALUES FOR REGISTRY_URL AND *_REGISTRY ARE NOT SET HERE; THEY ARE EITHER PROVIDED BY THE USER OR DETERMINED AT RUNTIME BASED ON WO_DEVELOPER_EDITION_SOURCE.
50
+ REGISTRY_URL=
52
51
 
53
- WORKER_TAG=29-04-2025
54
- WORKER_REGISTRY=watson-orchestrate-private
52
+ SERVER_TAG=03-05-2025
53
+ SERVER_REGISTRY=
55
54
 
56
- DB_REGISTRY=watson-orchestrate-private
55
+ WORKER_TAG=03-05-2025
56
+ WORKER_REGISTRY=
57
+
58
+ DB_REGISTRY=
57
59
  # If you build multiarch set all three of these to the same, we have a pr against main
58
60
  # to not have this separation, but we can merge it later
59
- DBTAG=29-04-2025
60
- AMDDBTAG=22-04-2025
61
- ARM64DBTAG=22-04-2025
61
+ DBTAG=02-05-2025
62
+ AMDDBTAG=02-05-2025
63
+ ARM64DBTAG=02-05-2025
62
64
 
63
- UI_REGISTRY=watson-orchestrate-private
64
- UITAG=29-04-2025
65
+ UI_REGISTRY=
66
+ UITAG=01-05-2025
65
67
 
66
- CM_REGISTRY=watson-orchestrate-private
68
+ CM_REGISTRY=
67
69
  CM_TAG=30-04-2025
68
70
 
69
- TRM_TAG=30-04-2025
70
- TRM_REGISTRY=watson-orchestrate-private
71
+ TRM_TAG=02-05-2025
72
+ TRM_REGISTRY=
71
73
 
72
- TR_TAG=30-04-2025
73
- TR_REGISTRY=watson-orchestrate-private
74
+ TR_TAG=02-05-2025
75
+ TR_REGISTRY=
74
76
 
75
- BUILDER_REGISTRY=watson-orchestrate-private
76
- BUILDER_TAG=29-04-2025
77
+ BUILDER_REGISTRY=
78
+ BUILDER_TAG=02-05-2025
77
79
 
78
80
  FLOW_RUNTIME_TAG=31-03-2025
81
+ FLOW_RUMTIME_REGISTRY=
79
82
 
80
83
  # END -- IMAGE REGISTRIES AND TAGS
81
84
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ibm-watsonx-orchestrate
3
- Version: 1.0.1
3
+ Version: 1.2.0
4
4
  Summary: IBM watsonx.orchestrate SDK
5
5
  Author-email: IBM <support@ibm.com>
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- ibm_watsonx_orchestrate/__init__.py,sha256=SrlQDGttogEX9VFQltBr-eZPkFA_rUB44zQv2zVKDN4,424
1
+ ibm_watsonx_orchestrate/__init__.py,sha256=QYUW_dfyamR2JbmoR89HSm5oP6_1q9B_-nA05eXp5BI,425
2
2
  ibm_watsonx_orchestrate/agent_builder/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  ibm_watsonx_orchestrate/agent_builder/agents/__init__.py,sha256=v4G0MGh11eOCkUJP_4AMOcFgzW14oE41G3iFp7G2vvw,376
4
4
  ibm_watsonx_orchestrate/agent_builder/agents/agent.py,sha256=PcBg2dRi-IOzvl24u8fa3B0jLaM5hzgkpTS8k56L9Ag,919
@@ -14,13 +14,13 @@ ibm_watsonx_orchestrate/agent_builder/knowledge_bases/types.py,sha256=yvtf4zM0Ms
14
14
  ibm_watsonx_orchestrate/agent_builder/tools/__init__.py,sha256=adkYX0wgB-RKFCUBw6LPJhNVelUjUdsxipGPk2ghLns,479
15
15
  ibm_watsonx_orchestrate/agent_builder/tools/base_tool.py,sha256=unJBOJUY8DAq3T3YX5d1H5KehJUCjObAdpGLVoWIfzw,1156
16
16
  ibm_watsonx_orchestrate/agent_builder/tools/openapi_tool.py,sha256=GD2yQkBSRHcnyq1LqA3crfo05rruqCj9vBS9xVDR2r0,14738
17
- ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py,sha256=wvJVC4VJuedkld_JFib1Le3o958bIvo7qhBr0VEI7wc,7940
18
- ibm_watsonx_orchestrate/agent_builder/tools/types.py,sha256=lA_BxWR_vQ0KTGYh3V0LPtNx9jG-IhwuptFf-VzhZAk,5280
17
+ ibm_watsonx_orchestrate/agent_builder/tools/python_tool.py,sha256=gh38bToYRnoqmsoR7pHCwqQTI6ENbxOPOV5aVSqECrg,8359
18
+ ibm_watsonx_orchestrate/agent_builder/tools/types.py,sha256=LcMEWTUYx_bAKK_gA8Uav-hSU5qsKl4_lJ7oWYgdfLc,5813
19
19
  ibm_watsonx_orchestrate/agent_builder/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  ibm_watsonx_orchestrate/agent_builder/utils/pydantic_utils.py,sha256=QEanM6FpkmntvS02whdhWx1d4v6zT_1l9ipEbfTgHs8,7623
21
21
  ibm_watsonx_orchestrate/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- ibm_watsonx_orchestrate/cli/config.py,sha256=v-Yse4QnjEFrs_sT90tjL86BRsmp1LDn2YJbyikGWQY,8018
23
- ibm_watsonx_orchestrate/cli/main.py,sha256=AZtBEb1Dm_gh1uo98AnMo-D_LKhsLIUrRx-xJFnprog,2177
22
+ ibm_watsonx_orchestrate/cli/config.py,sha256=C_iSP6WSb5SO6cVPTueQ9lEX16gYOLXD12cKXD6w_KQ,8168
23
+ ibm_watsonx_orchestrate/cli/main.py,sha256=Tey0gm3_z4JUbrKMSVFh_PEbCQqUGCohvnHRAU73KQA,2522
24
24
  ibm_watsonx_orchestrate/cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  ibm_watsonx_orchestrate/cli/commands/agents/agents_command.py,sha256=GVHM1wv28zGzOcc__3BXHt0Y5NXZuOr7PH8exwVqn-o,6640
26
26
  ibm_watsonx_orchestrate/cli/commands/agents/agents_controller.py,sha256=mOoBwLltv5dpGdawoyXClUIsO3bIpZLOXrIXZ7Cney4,27920
@@ -39,15 +39,17 @@ ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_command.py,
39
39
  ibm_watsonx_orchestrate/cli/commands/knowledge_bases/knowledge_bases_controller.py,sha256=mK9rMzslwIYDx8ebUs7QHVYAak6xm830_pKu3xH6F-s,8245
40
40
  ibm_watsonx_orchestrate/cli/commands/login/login_command.py,sha256=xArMiojoozg7Exn6HTpbTcjDO2idZRA-y0WV-_Ic1Sk,651
41
41
  ibm_watsonx_orchestrate/cli/commands/models/models_command.py,sha256=lxh0YpATogJRroHItwVuHmsZlHctVzWw_dEWkJlbwOI,4893
42
- ibm_watsonx_orchestrate/cli/commands/server/server_command.py,sha256=BRUUsZQddPSTSFQywFdJkqKtcxcWFwbIOf11WLx7sTQ,21976
42
+ ibm_watsonx_orchestrate/cli/commands/server/server_command.py,sha256=fleb0JwH1PJ67M4ArL5l4vxoJAK1mtsIRFjHWSsKenI,28774
43
43
  ibm_watsonx_orchestrate/cli/commands/settings/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
44
  ibm_watsonx_orchestrate/cli/commands/settings/settings_command.py,sha256=CzXRkd-97jXyS6LtaaNtMah-aZu0919dYl-mDwzGThc,344
45
45
  ibm_watsonx_orchestrate/cli/commands/settings/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  ibm_watsonx_orchestrate/cli/commands/settings/observability/observability_command.py,sha256=TAkpKwoqocsShSgEeR6LzHCzJx16VDQ6cYsbpljxeqI,372
47
47
  ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
48
  ibm_watsonx_orchestrate/cli/commands/settings/observability/langfuse/langfuse_command.py,sha256=Wa0L8E44EdxH9LdOvmnluLk_ApJVfTLauNOC1kV4W8k,6515
49
+ ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_command.py,sha256=hsVazoC25DUHtCFt3nmzkF5Eai59-tnNIJR51Emjk0E,2494
50
+ ibm_watsonx_orchestrate/cli/commands/toolkit/toolkit_controller.py,sha256=QRVyfXJAOHA4D5pgVcq7m4TnKwSSwwtyns3_rKc1Jcc,8499
49
51
  ibm_watsonx_orchestrate/cli/commands/tools/tools_command.py,sha256=2GK5AKwEYXsOZaASG15J8yNIPakI0NYkSXBTkeuHj6s,3343
50
- ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py,sha256=vjOncBJ3K7hGaEo-SjzURm_DZ_mToTithwuuzkjn82M,26468
52
+ ibm_watsonx_orchestrate/cli/commands/tools/tools_controller.py,sha256=i6JmF0sObMmIVWuWH2DVCirKLLW-NkFtj7n06dg3Yos,27688
51
53
  ibm_watsonx_orchestrate/cli/commands/tools/types.py,sha256=_md0GEa_cTH17NO_moWDY_LNdFvyEFQ1UVB9_FltYiA,173
52
54
  ibm_watsonx_orchestrate/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
55
  ibm_watsonx_orchestrate/client/base_api_client.py,sha256=0ozLUudIrQH0RTdKaX0Y5c35FRNZPTemaAp71AsoMsQ,4410
@@ -68,9 +70,10 @@ ibm_watsonx_orchestrate/client/connections/__init__.py,sha256=u821r2ZiYXLYNTknxd
68
70
  ibm_watsonx_orchestrate/client/connections/connections_client.py,sha256=aenYRfywM0lPlJWipFuy-Dnp2ljmpeP11oaWvPd69sA,7053
69
71
  ibm_watsonx_orchestrate/client/connections/utils.py,sha256=XqgYoiehiqIZ-LnduIAiJ9DIIF7IR7QvkKTc9784Ii0,1326
70
72
  ibm_watsonx_orchestrate/client/knowledge_bases/knowledge_base_client.py,sha256=U-pG_H0I8992f0V13Li_e1dksKp54MrYX3X9bvr-09w,2181
73
+ ibm_watsonx_orchestrate/client/toolkit/toolkit_client.py,sha256=1ZcOcjULV8xXqEVQpuDx_Nhh3dOFC2DA-RXMddl-wH0,2974
71
74
  ibm_watsonx_orchestrate/client/tools/tool_client.py,sha256=pEKOBH488YbLVc71ucucX0rr8YoulvDCxejuyWd0K8s,1588
72
- ibm_watsonx_orchestrate/docker/compose-lite.yml,sha256=T_d2xi20fNX8fwByhkAErXf21BLIWz-F2alsvvWhD9c,24575
73
- ibm_watsonx_orchestrate/docker/default.env,sha256=0dGbBljxmmxOxEGNP1vgVlQ9dxv9Ii6h_3p0QviEtnY,4639
75
+ ibm_watsonx_orchestrate/docker/compose-lite.yml,sha256=Br6wNrRNYVYeIpc4q_Usc-ENK0RzccE-67GSlYhwggw,24688
76
+ ibm_watsonx_orchestrate/docker/default.env,sha256=kuLLcXI24G4plLW8_7Af9K8VEVpt_zAhCOL1p-FVqS0,4609
74
77
  ibm_watsonx_orchestrate/docker/start-up.sh,sha256=LTtwHp0AidVgjohis2LXGvZnkFQStOiUAxgGABOyeUI,1811
75
78
  ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0-py3-none-any.whl,sha256=Hi3-owh5OM0Jz2ihX9nLoojnr7Ky1TV-GelyqLcewLE,2047417
76
79
  ibm_watsonx_orchestrate/docker/sdk/ibm_watsonx_orchestrate-0.6.0.tar.gz,sha256=e5T-q7XPAtiCyQljwZp6kk3Q_4Tg6y5sijHTkscmqqQ,2025466
@@ -82,8 +85,8 @@ ibm_watsonx_orchestrate/utils/utils.py,sha256=3JWk1J9A04yVZeetE3TQH82I53Sn20KvU_
82
85
  ibm_watsonx_orchestrate/utils/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
86
  ibm_watsonx_orchestrate/utils/logging/logger.py,sha256=FzeGnidXAjC7yHrvIaj4KZPeaBBSCniZFlwgr5yV3oA,1037
84
87
  ibm_watsonx_orchestrate/utils/logging/logging.yaml,sha256=9_TKfuFr1barnOKP0fZT5D6MhddiwsXVTFjtRbcOO5w,314
85
- ibm_watsonx_orchestrate-1.0.1.dist-info/METADATA,sha256=HAjcx8665Xir9Y1n3SkqrK1anhiDNs3bMvIItdHd1dY,1251
86
- ibm_watsonx_orchestrate-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
87
- ibm_watsonx_orchestrate-1.0.1.dist-info/entry_points.txt,sha256=SfIT02-Jen5e99OcLhzbcM9Bdyf8SGVOCtnSplgZdQI,69
88
- ibm_watsonx_orchestrate-1.0.1.dist-info/licenses/LICENSE,sha256=Shgxx7hTdCOkiVRmfGgp_1ISISrwQD7m2f0y8Hsapl4,1083
89
- ibm_watsonx_orchestrate-1.0.1.dist-info/RECORD,,
88
+ ibm_watsonx_orchestrate-1.2.0.dist-info/METADATA,sha256=ecvKQFIj26BZT-5M9fPjcfs371MR-NQIV89qrn6GNRQ,1251
89
+ ibm_watsonx_orchestrate-1.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
90
+ ibm_watsonx_orchestrate-1.2.0.dist-info/entry_points.txt,sha256=SfIT02-Jen5e99OcLhzbcM9Bdyf8SGVOCtnSplgZdQI,69
91
+ ibm_watsonx_orchestrate-1.2.0.dist-info/licenses/LICENSE,sha256=Shgxx7hTdCOkiVRmfGgp_1ISISrwQD7m2f0y8Hsapl4,1083
92
+ ibm_watsonx_orchestrate-1.2.0.dist-info/RECORD,,