uipath 2.1.102__py3-none-any.whl → 2.1.103__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.

Potentially problematic release.


This version of uipath might be problematic. Click here for more details.

@@ -341,6 +341,7 @@ class UiPathRuntimeContext(BaseModel):
341
341
  result: Optional[UiPathRuntimeResult] = None
342
342
  execution_output_file: Optional[str] = None
343
343
  input_file: Optional[str] = None
344
+ trace_file: Optional[str] = None
344
345
  is_eval_run: bool = False
345
346
  log_handler: Optional[logging.Handler] = None
346
347
  chat_handler: Optional[UiPathConversationHandler] = None
uipath/_cli/cli_run.py CHANGED
@@ -8,7 +8,7 @@ import click
8
8
 
9
9
  from uipath._cli._runtime._runtime_factory import generate_runtime_factory
10
10
  from uipath._cli._utils._debug import setup_debugging
11
- from uipath.tracing import LlmOpsHttpExporter
11
+ from uipath.tracing import JsonLinesFileExporter, LlmOpsHttpExporter
12
12
 
13
13
  from .._utils.constants import (
14
14
  ENV_JOB_ID,
@@ -44,6 +44,12 @@ console = ConsoleLogger()
44
44
  type=click.Path(exists=False),
45
45
  help="File path where the output will be written",
46
46
  )
47
+ @click.option(
48
+ "--trace-file",
49
+ required=False,
50
+ type=click.Path(exists=False),
51
+ help="File path where the trace spans will be written (JSON Lines format)",
52
+ )
47
53
  @click.option(
48
54
  "--debug",
49
55
  is_flag=True,
@@ -63,6 +69,7 @@ def run(
63
69
  file: Optional[str],
64
70
  input_file: Optional[str],
65
71
  output_file: Optional[str],
72
+ trace_file: Optional[str],
66
73
  debug: bool,
67
74
  debug_port: int,
68
75
  ) -> None:
@@ -73,6 +80,7 @@ def run(
73
80
  "resume": resume,
74
81
  "input_file": file or input_file,
75
82
  "execution_output_file": output_file,
83
+ "trace_file": trace_file,
76
84
  "debug": debug,
77
85
  }
78
86
  input_file = file or input_file
@@ -87,6 +95,7 @@ def run(
87
95
  resume,
88
96
  input_file=input_file,
89
97
  execution_output_file=output_file,
98
+ trace_file=trace_file,
90
99
  debug=debug,
91
100
  debug_port=debug_port,
92
101
  )
@@ -110,7 +119,12 @@ def run(
110
119
  context = runtime_factory.new_context(**context_args)
111
120
  if context.job_id:
112
121
  runtime_factory.add_span_exporter(LlmOpsHttpExporter())
122
+
123
+ if trace_file:
124
+ runtime_factory.add_span_exporter(JsonLinesFileExporter(trace_file))
125
+
113
126
  result = await runtime_factory.execute(context)
127
+
114
128
  if not context.job_id:
115
129
  console.info(result.output)
116
130
 
@@ -65,6 +65,7 @@ uv run uipath init --infer-bindings
65
65
  | `-f`, `--file` | value | none | File path for the .json input |
66
66
  | `--input-file` | value | none | Alias for '-f/--file' arguments |
67
67
  | `--output-file` | value | none | File path where the output will be written |
68
+ | `--trace-file` | value | none | File path where the trace spans will be written (JSON Lines format) |
68
69
  | `--debug` | flag | false | Enable debugging with debugpy. The process will wait for a debugger to attach. |
69
70
  | `--debug-port` | value | `5678` | Port for the debug server (default: 5678) |
70
71
 
@@ -1,4 +1,13 @@
1
- from ._otel_exporters import LlmOpsHttpExporter # noqa: D104
1
+ from ._otel_exporters import ( # noqa: D104
2
+ JsonLinesFileExporter,
3
+ LlmOpsHttpExporter,
4
+ )
2
5
  from ._traced import TracingManager, traced, wait_for_tracers # noqa: D104
3
6
 
4
- __all__ = ["TracingManager", "traced", "wait_for_tracers", "LlmOpsHttpExporter"]
7
+ __all__ = [
8
+ "TracingManager",
9
+ "traced",
10
+ "wait_for_tracers",
11
+ "LlmOpsHttpExporter",
12
+ "JsonLinesFileExporter",
13
+ ]
@@ -2,7 +2,7 @@ import json
2
2
  import logging
3
3
  import os
4
4
  import time
5
- from typing import Any, Dict, Optional, Sequence
5
+ from typing import Any, Dict, List, Optional, Sequence
6
6
 
7
7
  import httpx
8
8
  from opentelemetry.sdk.trace import ReadableSpan
@@ -18,18 +18,88 @@ from ._utils import _SpanUtils
18
18
  logger = logging.getLogger(__name__)
19
19
 
20
20
 
21
+ def _safe_parse_json(s: Any) -> Any:
22
+ """Safely parse a JSON string, returning the original if not a string or on error."""
23
+ if not isinstance(s, str):
24
+ return s
25
+ try:
26
+ return json.loads(s)
27
+ except (json.JSONDecodeError, TypeError):
28
+ return s
29
+
30
+
31
+ def _get_llm_messages(attributes: Dict[str, Any], prefix: str) -> List[Dict[str, Any]]:
32
+ """Extracts and reconstructs LLM messages from flattened attributes."""
33
+ messages: dict[int, dict[str, Any]] = {}
34
+ message_prefix = f"{prefix}."
35
+
36
+ for key, value in attributes.items():
37
+ if key.startswith(message_prefix):
38
+ parts = key[len(message_prefix) :].split(".")
39
+ if len(parts) >= 2 and parts[0].isdigit():
40
+ index = int(parts[0])
41
+ if index not in messages:
42
+ messages[index] = {}
43
+ current: Any = messages[index]
44
+
45
+ for i, part in enumerate(parts[1:-1]):
46
+ key_part: str | int = part
47
+ if part.isdigit() and (
48
+ i + 2 < len(parts) and parts[i + 2].isdigit()
49
+ ):
50
+ key_part = int(part)
51
+
52
+ if isinstance(current, dict):
53
+ if key_part not in current:
54
+ current[key_part] = {}
55
+ current = current[key_part]
56
+ elif isinstance(current, list) and isinstance(key_part, int):
57
+ if key_part >= len(current):
58
+ current.append({})
59
+ current = current[key_part]
60
+
61
+ current[parts[-1]] = value
62
+
63
+ # Convert dict to list, ordered by index
64
+ return [messages[i] for i in sorted(messages.keys())]
65
+
66
+
21
67
  class LlmOpsHttpExporter(SpanExporter):
22
68
  """An OpenTelemetry span exporter that sends spans to UiPath LLM Ops."""
23
69
 
24
- def __init__(self, trace_id: Optional[str] = None, **client_kwargs):
70
+ ATTRIBUTE_MAPPING: dict[str, str | tuple[str, Any]] = {
71
+ "input.value": ("input", _safe_parse_json),
72
+ "output.value": ("output", _safe_parse_json),
73
+ "llm.model_name": "model",
74
+ }
75
+
76
+ # Mapping of span types
77
+ SPAN_TYPE_MAPPING: dict[str, str] = {
78
+ "LLM": "completion",
79
+ "TOOL": "toolCall",
80
+ # Add more mappings as needed
81
+ }
82
+
83
+ class Status:
84
+ SUCCESS = 1
85
+ ERROR = 2
86
+ INTERRUPTED = 3
87
+
88
+ def __init__(
89
+ self,
90
+ trace_id: Optional[str] = None,
91
+ extra_process_spans: Optional[bool] = False,
92
+ **kwargs,
93
+ ):
25
94
  """Initialize the exporter with the base URL and authentication token."""
26
- super().__init__(**client_kwargs)
95
+ super().__init__(**kwargs)
27
96
  self.base_url = self._get_base_url()
28
97
  self.auth_token = os.environ.get("UIPATH_ACCESS_TOKEN")
29
98
  self.headers = {
30
99
  "Content-Type": "application/json",
31
100
  "Authorization": f"Bearer {self.auth_token}",
32
101
  }
102
+ self._extra_process_spans = extra_process_spans
33
103
 
34
104
  client_kwargs = get_httpx_client_kwargs()
35
105
 
@@ -50,6 +120,9 @@ class LlmOpsHttpExporter(SpanExporter):
50
120
  ]
51
121
  url = self._build_url(span_list)
52
122
 
123
+ if self._extra_process_spans:
124
+ span_list = [self._process_span_attributes(span) for span in span_list]
125
+
53
126
  logger.debug("Payload: %s", json.dumps(span_list))
54
127
 
55
128
  return self._send_with_retries(url, span_list)
@@ -58,6 +131,150 @@ class LlmOpsHttpExporter(SpanExporter):
58
131
  """Force flush the exporter."""
59
132
  return True
60
133
 
134
+ def _map_llm_call_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
135
+ """Maps attributes for LLM calls, handling flattened keys."""
136
+ result = attributes.copy() # Keep original attributes including basic mappings
137
+
138
+ # Token Usage
139
+ token_keys = {
140
+ "llm.token_count.prompt": "promptTokens",
141
+ "llm.token_count.completion": "completionTokens",
142
+ "llm.token_count.total": "totalTokens",
143
+ }
144
+ usage = {
145
+ new_key: attributes.get(old_key)
146
+ for old_key, new_key in token_keys.items()
147
+ if old_key in attributes
148
+ }
149
+ if usage:
150
+ result["usage"] = usage
151
+
152
+ # Input/Output Messages
153
+ result["input"] = _get_llm_messages(attributes, "llm.input_messages")
154
+ output_messages = _get_llm_messages(attributes, "llm.output_messages")
155
+ result["output"] = output_messages
156
+
157
+ # Invocation Parameters
158
+ invocation_params = _safe_parse_json(
159
+ attributes.get("llm.invocation_parameters", "{}")
160
+ )
161
+ if isinstance(invocation_params, dict):
162
+ result["model"] = invocation_params.get("model", result.get("model"))
163
+ settings: dict[str, Any] = {}
164
+ if "max_tokens" in invocation_params:
165
+ settings["maxTokens"] = invocation_params["max_tokens"]
166
+ if "temperature" in invocation_params:
167
+ settings["temperature"] = invocation_params["temperature"]
168
+ if settings:
169
+ result["settings"] = settings
170
+
171
+ # Tool Calls
172
+ tool_calls: list[dict[str, Any]] = []
173
+ for msg in output_messages:
174
+ # Ensure msg is a dictionary before proceeding
175
+ if not isinstance(msg, dict):
176
+ continue
177
+ msg_tool_calls = msg.get("message", {}).get("tool_calls", [])
178
+
179
+ # Ensure msg_tool_calls is a list
180
+ if not isinstance(msg_tool_calls, list):
181
+ continue
182
+
183
+ for tc in msg_tool_calls:
184
+ if not isinstance(tc, dict):
185
+ continue
186
+ tool_call_data = tc.get("tool_call", {})
187
+ if not isinstance(tool_call_data, dict):
188
+ continue
189
+ tool_calls.append(
190
+ {
191
+ "id": tool_call_data.get("id"),
192
+ "name": tool_call_data.get("function", {}).get("name"),
193
+ "arguments": _safe_parse_json(
194
+ tool_call_data.get("function", {}).get("arguments", "{}")
195
+ ),
196
+ }
197
+ )
198
+ if tool_calls:
199
+ result["toolCalls"] = tool_calls
200
+
201
+ return result
202
+
203
+ def _map_tool_call_attributes(self, attributes: Dict[str, Any]) -> Dict[str, Any]:
204
+ """Maps attributes for tool calls."""
205
+ result = attributes.copy() # Keep original attributes
206
+
207
+ result["type"] = "toolCall"
208
+ result["callId"] = attributes.get("call_id") or attributes.get("id")
209
+ result["toolName"] = attributes.get("tool.name")
210
+ result["arguments"] = _safe_parse_json(
211
+ attributes.get("input", attributes.get("input.value", "{}"))
212
+ )
213
+ result["toolType"] = "Integration"
214
+ result["result"] = _safe_parse_json(
215
+ attributes.get("output", attributes.get("output.value"))
216
+ )
217
+ result["error"] = None
218
+
219
+ return result
220
+
221
+ def _determine_status(self, error: Optional[str]) -> int:
222
+ if error:
223
+ if error and error.startswith("GraphInterrupt("):
224
+ return self.Status.INTERRUPTED
225
+ return self.Status.ERROR
226
+ return self.Status.SUCCESS
227
+
228
+ def _process_span_attributes(self, span_data: Dict[str, Any]) -> Dict[str, Any]:
229
+ """Extracts, transforms, and maps attributes for a span."""
230
+ if "Attributes" not in span_data:
231
+ return span_data
232
+
233
+ attributes_val = span_data["Attributes"]
234
+ if isinstance(attributes_val, str):
235
+ try:
236
+ attributes: Dict[str, Any] = json.loads(attributes_val)
237
+ except json.JSONDecodeError as e:
238
+ logger.warning(f"Failed to parse attributes JSON: {e}")
239
+ return span_data
240
+ elif isinstance(attributes_val, dict):
241
+ attributes = attributes_val
242
+ else:
243
+ return span_data
244
+
245
+ # Determine SpanType
246
+ if "openinference.span.kind" in attributes:
247
+ span_type = attributes["openinference.span.kind"]
248
+ span_data["SpanType"] = self.SPAN_TYPE_MAPPING.get(span_type, span_type)
249
+
250
+ # Apply basic attribute mapping
251
+ for old_key, mapping in self.ATTRIBUTE_MAPPING.items():
252
+ if old_key in attributes:
253
+ if isinstance(mapping, tuple):
254
+ new_key, func = mapping
255
+ attributes[new_key] = func(attributes[old_key])
256
+ else:
257
+ new_key = mapping
258
+ attributes[new_key] = attributes[old_key]
259
+
260
+ # Apply detailed mapping based on SpanType
261
+ span_type = span_data.get("SpanType")
262
+ if span_type == "completion":
263
+ processed_attributes = self._map_llm_call_attributes(attributes)
264
+ elif span_type == "toolCall":
265
+ processed_attributes = self._map_tool_call_attributes(attributes)
266
+ else:
267
+ processed_attributes = attributes.copy()
268
+
269
+ span_data["Attributes"] = json.dumps(processed_attributes)
270
+
271
+ # Determine status based on error information
272
+ error = attributes.get("error") or attributes.get("exception.message")
273
+ status = self._determine_status(error)
274
+ span_data["Status"] = status
275
+
276
+ return span_data
277
+
61
278
  def _build_url(self, span_list: list[Dict[str, Any]]) -> str:
62
279
  """Construct the URL for the API request."""
63
280
  trace_id = str(span_list[0]["TraceId"])
@@ -93,3 +310,30 @@ class LlmOpsHttpExporter(SpanExporter):
93
310
  uipath_url = uipath_url.rstrip("/")
94
311
 
95
312
  return uipath_url
313
+
314
+
315
+ class JsonLinesFileExporter(SpanExporter):
316
+ def __init__(self, file_path: str):
317
+ self.file_path = file_path
318
+ # Ensure the directory exists
319
+ dir_path = os.path.dirname(self.file_path)
320
+ if dir_path: # Only create if there's an actual directory path
321
+ os.makedirs(dir_path, exist_ok=True)
322
+
323
+ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
324
+ try:
325
+ uipath_spans = [
326
+ _SpanUtils.otel_span_to_uipath_span(span).to_dict() for span in spans
327
+ ]
328
+
329
+ with open(self.file_path, "a") as f:
330
+ for span in uipath_spans:
331
+ f.write(json.dumps(span) + "\n")
332
+ return SpanExportResult.SUCCESS
333
+ except Exception as e:
334
+ logger.error(f"Failed to export spans to {self.file_path}: {e}")
335
+ return SpanExportResult.FAILURE
336
+
337
+ def shutdown(self) -> None:
338
+ """Shuts down the exporter."""
339
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath
3
- Version: 2.1.102
3
+ Version: 2.1.103
4
4
  Summary: Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools.
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-python
@@ -18,7 +18,7 @@ uipath/_cli/cli_pack.py,sha256=U5rXVbUnHFgdEsXyhkjmWza8dfob1wU9lyl4yrYnUss,11076
18
18
  uipath/_cli/cli_publish.py,sha256=DgyfcZjvfV05Ldy0Pk5y_Le_nT9JduEE_x-VpIc_Kq0,6471
19
19
  uipath/_cli/cli_pull.py,sha256=v0iL12Z0NGE2yZ_Ff1U1jEgOnbIGJatI63KhZRIVflg,2681
20
20
  uipath/_cli/cli_push.py,sha256=cwKUr30VTvb2jhGY9LPpPpILj2URMuEIl8ven6tCRFo,3727
21
- uipath/_cli/cli_run.py,sha256=4XEvJQEcFafDLJHbgd94cuYxeioprNFe1qwL7JeAplg,3789
21
+ uipath/_cli/cli_run.py,sha256=9vbmSG_FzS0x8KNNzSrST37FKjUOzhXPjl8xTm2ZEqQ,4203
22
22
  uipath/_cli/middlewares.py,sha256=tb0c4sU1SCYi0PNs956Qmk24NDk0C0mBfVQmTcyORE0,5000
23
23
  uipath/_cli/spinner.py,sha256=bS-U_HA5yne11ejUERu7CQoXmWdabUD2bm62EfEdV8M,1107
24
24
  uipath/_cli/_auth/_auth_server.py,sha256=v_b8KNwn0tAv8jxpeKdllOVzl31q9AcdwpE_koAK_w4,7235
@@ -66,7 +66,7 @@ uipath/_cli/_evals/mocks/mocker_factory.py,sha256=V5QKSTtQxztTo4-fK1TyAaXw2Z3mHf
66
66
  uipath/_cli/_evals/mocks/mockito_mocker.py,sha256=AO2BmFwA6hz3Lte-STVr7aJDPvMCqKNKa4j2jeNZ_U4,2677
67
67
  uipath/_cli/_evals/mocks/mocks.py,sha256=HY0IaSqqO8hioBB3rp5XwAjSpQE4K5hoH6oJQ-sH72I,2207
68
68
  uipath/_cli/_push/sw_file_handler.py,sha256=DrGOpX7-dodrROh7YcjHlCBUuOEdVMh8o0550TL-ZYA,22520
69
- uipath/_cli/_runtime/_contracts.py,sha256=WqHEMBo5jM5a-yV-KD0FeOkei4M3qq-hQwza2hCe9Rk,34318
69
+ uipath/_cli/_runtime/_contracts.py,sha256=H7BeQ8MXB_-NsqqYDACDj5T1ra9eY7-YtHqRFVkmxjk,34355
70
70
  uipath/_cli/_runtime/_escalation.py,sha256=x3vI98qsfRA-fL_tNkRVTFXioM5Gv2w0GFcXJJ5eQtg,7981
71
71
  uipath/_cli/_runtime/_hitl.py,sha256=VKbM021nVg1HEDnTfucSLJ0LsDn83CKyUtVzofS2qTU,11369
72
72
  uipath/_cli/_runtime/_logging.py,sha256=srjAi3Cy6g7b8WNHiYNjaZT4t40F3XRqquuoGd2kh4Y,14019
@@ -98,7 +98,7 @@ uipath/_events/_event_bus.py,sha256=4-VzstyX69cr7wT1EY7ywp-Ndyz2CyemD3Wk_-QmRpo,
98
98
  uipath/_events/_events.py,sha256=eCgqEP4rzDgZShXMC9wgBVx-AcpwaJZlo2yiL7OyxdA,4441
99
99
  uipath/_resources/AGENTS.md,sha256=nRQNAVeEBaBvuMzXw8uXtMnGebLClUgwIMlgb8_qU9o,1039
100
100
  uipath/_resources/CLAUDE.md,sha256=kYsckFWTVe948z_fNWLysCHvi9_YpchBXl3s1Ek03lU,10
101
- uipath/_resources/CLI_REFERENCE.md,sha256=-eCe1hZSRvv9QGLltE3xl8muGWGNzmaHX8htRt3cgy8,6366
101
+ uipath/_resources/CLI_REFERENCE.md,sha256=M_SCtSjRhj1XwfgSFLfHJJahYXEd_CSQ_EnjLQAn-2Y,6470
102
102
  uipath/_resources/REQUIRED_STRUCTURE.md,sha256=3laqGiNa3kauJ7jRI1d7w_fWKUDkqYBjcTT_6_8FAGk,1417
103
103
  uipath/_resources/SDK_REFERENCE.md,sha256=4wX8a1W5EJCta-iEHy_cDRahn0ENpJykwn-w4k_Lh6s,23245
104
104
  uipath/_services/__init__.py,sha256=_LNy4u--VlhVtTO66bULbCoBjyJBTuyh9jnzjWrv-h4,1140
@@ -182,15 +182,15 @@ uipath/models/queues.py,sha256=gnbeEyYlHtdqdxBalio0lw8mq-78YBG9MPMSkv1BWOg,6934
182
182
  uipath/telemetry/__init__.py,sha256=Wna32UFzZR66D-RzTKlPWlvji9i2HJb82NhHjCCXRjY,61
183
183
  uipath/telemetry/_constants.py,sha256=uRDuEZayBYtBA0tMx-2AS_D-oiVA7oKgp9zid9jNats,763
184
184
  uipath/telemetry/_track.py,sha256=3RZgJtY8y28Y5rfVmC432OyRu7N3pSxPouwa82KWFso,4787
185
- uipath/tracing/__init__.py,sha256=GKRINyWdHVrDsI-8mrZDLdf0oey6GHGlNZTOADK-kgc,224
186
- uipath/tracing/_otel_exporters.py,sha256=c0GKU_oUrAwrOrqbyu64c55z1TR6xk01d3y5fLUN1lU,3215
185
+ uipath/tracing/__init__.py,sha256=0oUuxJKOHE14iOL4SP93FOiEYRIFLTq-o0NwRcTB8Q4,317
186
+ uipath/tracing/_otel_exporters.py,sha256=LAtaQV5QN4PSLgEbIVvlmUETNbMYqTX87R-3mRKy_S8,12438
187
187
  uipath/tracing/_traced.py,sha256=yBIY05PCCrYyx50EIHZnwJaKNdHPNx-YTR1sHQl0a98,19901
188
188
  uipath/tracing/_utils.py,sha256=X-LFsyIxDeNOGuHPvkb6T5o9Y8ElYhr_rP3CEBJSu4s,13837
189
189
  uipath/utils/__init__.py,sha256=VD-KXFpF_oWexFg6zyiWMkxl2HM4hYJMIUDZ1UEtGx0,105
190
190
  uipath/utils/_endpoints_manager.py,sha256=tnF_FiCx8qI2XaJDQgYkMN_gl9V0VqNR1uX7iawuLp8,8230
191
191
  uipath/utils/dynamic_schema.py,sha256=w0u_54MoeIAB-mf3GmwX1A_X8_HDrRy6p998PvX9evY,3839
192
- uipath-2.1.102.dist-info/METADATA,sha256=PHCMM28vCj3vTI7svQmDPoKJkg7J-YlGWUyLPFDFD-s,6626
193
- uipath-2.1.102.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
194
- uipath-2.1.102.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
195
- uipath-2.1.102.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
196
- uipath-2.1.102.dist-info/RECORD,,
192
+ uipath-2.1.103.dist-info/METADATA,sha256=lWydWnxXFj8zjUQgdUbcrWNJxfuOGM3RCqAoYPTL9oI,6626
193
+ uipath-2.1.103.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
194
+ uipath-2.1.103.dist-info/entry_points.txt,sha256=9C2_29U6Oq1ExFu7usihR-dnfIVNSKc-0EFbh0rskB4,43
195
+ uipath-2.1.103.dist-info/licenses/LICENSE,sha256=-KBavWXepyDjimmzH5fVAsi-6jNVpIKFc2kZs0Ri4ng,1058
196
+ uipath-2.1.103.dist-info/RECORD,,