uipath 2.1.36__py3-none-any.whl → 2.1.38__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 (29) hide show
  1. uipath/_cli/_dev/_terminal/__init__.py +70 -31
  2. uipath/_cli/_dev/_terminal/_components/_chat.py +106 -0
  3. uipath/_cli/_dev/_terminal/_components/_details.py +31 -15
  4. uipath/_cli/_dev/_terminal/_components/_history.py +16 -0
  5. uipath/_cli/_dev/_terminal/_models/_execution.py +16 -3
  6. uipath/_cli/_dev/_terminal/_models/_messages.py +3 -2
  7. uipath/_cli/_dev/_terminal/_styles/terminal.tcss +30 -2
  8. uipath/_cli/_dev/_terminal/_utils/_chat.py +240 -0
  9. uipath/_cli/_dev/_terminal/{_traces → _utils}/_logger.py +3 -3
  10. uipath/_cli/_runtime/_contracts.py +14 -0
  11. uipath/_cli/cli_dev.py +31 -7
  12. uipath/_cli/cli_init.py +3 -1
  13. uipath/agent/conversation/__init__.py +135 -0
  14. uipath/agent/conversation/async_stream.py +54 -0
  15. uipath/agent/conversation/citation.py +70 -0
  16. uipath/agent/conversation/content.py +81 -0
  17. uipath/agent/conversation/conversation.py +49 -0
  18. uipath/agent/conversation/event.py +56 -0
  19. uipath/agent/conversation/exchange.py +61 -0
  20. uipath/agent/conversation/message.py +59 -0
  21. uipath/agent/conversation/meta.py +11 -0
  22. uipath/agent/conversation/tool.py +64 -0
  23. {uipath-2.1.36.dist-info → uipath-2.1.38.dist-info}/METADATA +2 -1
  24. {uipath-2.1.36.dist-info → uipath-2.1.38.dist-info}/RECORD +28 -17
  25. uipath/_cli/_dev/_terminal/_components/_resume.py +0 -35
  26. /uipath/_cli/_dev/_terminal/{_traces → _utils}/_exporter.py +0 -0
  27. {uipath-2.1.36.dist-info → uipath-2.1.38.dist-info}/WHEEL +0 -0
  28. {uipath-2.1.36.dist-info → uipath-2.1.38.dist-info}/entry_points.txt +0 -0
  29. {uipath-2.1.36.dist-info → uipath-2.1.38.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,240 @@
1
+ import uuid
2
+ from datetime import datetime
3
+ from typing import Callable, Dict, Optional
4
+
5
+ from uipath.agent.conversation import (
6
+ UiPathConversationContentPart,
7
+ UiPathConversationContentPartChunkEvent,
8
+ UiPathConversationContentPartEndEvent,
9
+ UiPathConversationContentPartEvent,
10
+ UiPathConversationContentPartStartEvent,
11
+ UiPathConversationEvent,
12
+ UiPathConversationExchangeEvent,
13
+ UiPathConversationMessage,
14
+ UiPathConversationMessageEndEvent,
15
+ UiPathConversationMessageEvent,
16
+ UiPathConversationMessageStartEvent,
17
+ UiPathConversationToolCall,
18
+ UiPathConversationToolCallResult,
19
+ UiPathInlineValue,
20
+ )
21
+
22
+ from ...._runtime._contracts import UiPathConversationHandler
23
+
24
+
25
+ class RunContextChatHandler(UiPathConversationHandler):
26
+ """Custom chat handler that sends chat messages to CLI UI."""
27
+
28
+ def __init__(
29
+ self,
30
+ run_id: str,
31
+ callback: Callable[[UiPathConversationEvent, str], None],
32
+ ):
33
+ super().__init__()
34
+ self.run_id = run_id
35
+ self.callback = callback
36
+
37
+ def on_event(self, event: UiPathConversationEvent) -> None:
38
+ """Handle a chat message for a given execution run."""
39
+ self.callback(event, self.run_id)
40
+
41
+
42
+ class ConversationAggregator:
43
+ """Incrementally builds messages from UiPathConversationEvents."""
44
+
45
+ messages: Dict[str, UiPathConversationMessage]
46
+
47
+ def __init__(self) -> None:
48
+ self.messages = {}
49
+
50
+ def add_event(
51
+ self, event: UiPathConversationEvent
52
+ ) -> Optional[UiPathConversationMessage]:
53
+ """Process an incoming conversation-level event and return the current message snapshot if applicable."""
54
+ if not event.exchange or not event.exchange.message:
55
+ return None
56
+
57
+ msg_event = event.exchange.message
58
+ return self._process_message_event(msg_event)
59
+
60
+ def _process_message_event(
61
+ self, ev: UiPathConversationMessageEvent
62
+ ) -> UiPathConversationMessage:
63
+ msg = self.messages.get(ev.message_id)
64
+
65
+ if not msg:
66
+ # Initialize message on first sighting
67
+ msg = UiPathConversationMessage(
68
+ message_id=ev.message_id,
69
+ role=self._infer_role(ev),
70
+ content_parts=[],
71
+ tool_calls=[],
72
+ created_at=self._timestamp(ev),
73
+ updated_at=self._timestamp(ev),
74
+ )
75
+ self.messages[ev.message_id] = msg
76
+
77
+ # --- Handle content parts (text, JSON, etc.) ---
78
+ if ev.content_part:
79
+ cp_event = ev.content_part
80
+
81
+ existing = next(
82
+ (
83
+ cp
84
+ for cp in (msg.content_parts or [])
85
+ if cp.content_part_id == cp_event.content_part_id
86
+ ),
87
+ None,
88
+ )
89
+
90
+ # Start of a new content part
91
+ if cp_event.start and not existing:
92
+ new_cp = UiPathConversationContentPart(
93
+ content_part_id=cp_event.content_part_id,
94
+ mime_type=cp_event.start.mime_type,
95
+ data=UiPathInlineValue(inline=""),
96
+ citations=[],
97
+ is_transcript=None,
98
+ is_incomplete=True,
99
+ )
100
+ if msg.content_parts is None:
101
+ msg.content_parts = []
102
+ msg.content_parts.append(new_cp)
103
+ existing = new_cp
104
+
105
+ # Chunk for an existing part (or backfill if start missing)
106
+ if cp_event.chunk:
107
+ if not existing:
108
+ new_cp = UiPathConversationContentPart(
109
+ content_part_id=cp_event.content_part_id,
110
+ mime_type="text/plain", # fallback if start missing
111
+ data=UiPathInlineValue(inline=""),
112
+ citations=[],
113
+ is_transcript=None,
114
+ is_incomplete=True,
115
+ )
116
+ if msg.content_parts is None:
117
+ msg.content_parts = []
118
+ msg.content_parts.append(new_cp)
119
+ existing = new_cp
120
+
121
+ if isinstance(existing.data, UiPathInlineValue):
122
+ existing.data.inline += cp_event.chunk.data or ""
123
+
124
+ # End of a part
125
+ if cp_event.end and existing:
126
+ existing.is_incomplete = bool(cp_event.end.interrupted)
127
+
128
+ # --- Handle tool calls ---
129
+ if ev.tool_call:
130
+ tc_event = ev.tool_call
131
+ existing_tool_call = next(
132
+ (
133
+ tc
134
+ for tc in (msg.tool_calls or [])
135
+ if tc.tool_call_id == tc_event.tool_call_id
136
+ ),
137
+ None,
138
+ )
139
+
140
+ # Start of a tool call
141
+ if tc_event.start:
142
+ if not existing_tool_call:
143
+ new_tc = UiPathConversationToolCall(
144
+ tool_call_id=tc_event.tool_call_id,
145
+ name=tc_event.start.tool_name,
146
+ arguments=None, # args will arrive as JSON content part
147
+ timestamp=tc_event.start.timestamp,
148
+ result=None,
149
+ )
150
+ if msg.tool_calls is None:
151
+ msg.tool_calls = []
152
+ msg.tool_calls.append(new_tc)
153
+ existing_tool_call = new_tc
154
+ else:
155
+ existing_tool_call.name = (
156
+ tc_event.start.tool_name or existing_tool_call.name
157
+ )
158
+ existing_tool_call.timestamp = (
159
+ tc_event.start.timestamp or existing_tool_call.timestamp
160
+ )
161
+
162
+ # End of a tool call
163
+ if tc_event.end:
164
+ if not existing_tool_call:
165
+ existing_tool_call = UiPathConversationToolCall(
166
+ tool_call_id=tc_event.tool_call_id,
167
+ name="", # unknown until start seen
168
+ arguments=None,
169
+ )
170
+ if msg.tool_calls is None:
171
+ msg.tool_calls = []
172
+ msg.tool_calls.append(existing_tool_call)
173
+
174
+ existing_tool_call.result = UiPathConversationToolCallResult(
175
+ timestamp=tc_event.end.timestamp,
176
+ value=tc_event.end.result,
177
+ is_error=tc_event.end.is_error,
178
+ cancelled=tc_event.end.cancelled,
179
+ )
180
+
181
+ # --- Update timestamps ---
182
+ msg.updated_at = self._timestamp(ev)
183
+ return msg
184
+
185
+ def _timestamp(self, ev: UiPathConversationMessageEvent) -> str:
186
+ """Choose timestamp from event if available, else fallback."""
187
+ if ev.start and ev.start.timestamp:
188
+ return ev.start.timestamp
189
+ return datetime.now().isoformat()
190
+
191
+ def _infer_role(self, ev: UiPathConversationMessageEvent) -> str:
192
+ if ev.start and ev.start.role:
193
+ return ev.start.role
194
+ return "assistant"
195
+
196
+
197
+ def build_user_message_event(
198
+ user_text: str, conversation_id: str, role: str = "user"
199
+ ) -> UiPathConversationEvent:
200
+ message_id = str(uuid.uuid4())
201
+ content_part_id = str(uuid.uuid4())
202
+ timestamp = datetime.now().isoformat()
203
+
204
+ # Build message start
205
+ msg_start = UiPathConversationMessageStartEvent(
206
+ role=role,
207
+ timestamp=timestamp,
208
+ )
209
+
210
+ # Build content part (simple one-shot inline text)
211
+ cp_start = UiPathConversationContentPartStartEvent(mime_type="text/plain")
212
+ cp_chunk = UiPathConversationContentPartChunkEvent(data=user_text)
213
+ cp_end = UiPathConversationContentPartEndEvent()
214
+
215
+ content_event = UiPathConversationContentPartEvent(
216
+ content_part_id=content_part_id,
217
+ start=cp_start,
218
+ chunk=cp_chunk,
219
+ end=cp_end,
220
+ )
221
+
222
+ # Build message event
223
+ msg_event = UiPathConversationMessageEvent(
224
+ message_id=message_id,
225
+ start=msg_start,
226
+ content_part=content_event,
227
+ end=UiPathConversationMessageEndEvent(),
228
+ )
229
+
230
+ # Wrap in exchange event
231
+ exch_event = UiPathConversationExchangeEvent(
232
+ exchange_id=str(uuid.uuid4()),
233
+ message=msg_event,
234
+ )
235
+
236
+ # Finally wrap in conversation event
237
+ conv_event = UiPathConversationEvent(
238
+ exchange=exch_event, conversation_id=conversation_id
239
+ )
240
+ return conv_event
@@ -11,11 +11,11 @@ class RunContextLogHandler(logging.Handler):
11
11
  def __init__(
12
12
  self,
13
13
  run_id: str,
14
- on_log: Callable[[LogMessage], None],
14
+ callback: Callable[[LogMessage], None],
15
15
  ):
16
16
  super().__init__()
17
17
  self.run_id = run_id
18
- self.on_log = on_log
18
+ self.callback = callback
19
19
 
20
20
  def emit(self, record: logging.LogRecord):
21
21
  """Emit a log record to CLI UI."""
@@ -26,7 +26,7 @@ class RunContextLogHandler(logging.Handler):
26
26
  message=self.format(record),
27
27
  timestamp=datetime.fromtimestamp(record.created),
28
28
  )
29
- self.on_log(log_msg)
29
+ self.callback(log_msg)
30
30
  except Exception:
31
31
  # Don't let logging errors crash the app
32
32
  pass
@@ -22,6 +22,7 @@ from opentelemetry.sdk.trace.export import (
22
22
  from opentelemetry.trace import Tracer
23
23
  from pydantic import BaseModel, Field
24
24
 
25
+ from uipath.agent.conversation import UiPathConversationEvent
25
26
  from uipath.tracing import TracingManager
26
27
 
27
28
  from ._logging import LogsInterceptor
@@ -134,6 +135,17 @@ class UiPathRuntimeResult(BaseModel):
134
135
  return result
135
136
 
136
137
 
138
+ class UiPathConversationHandler(ABC):
139
+ """Base delegate for handling UiPath conversation events."""
140
+
141
+ use_streaming: bool = True
142
+
143
+ @abstractmethod
144
+ def on_event(self, event: UiPathConversationEvent) -> None:
145
+ """Handle a conversation event for a given execution run."""
146
+ pass
147
+
148
+
137
149
  class UiPathTraceContext(BaseModel):
138
150
  """Trace context information for tracing and debugging."""
139
151
 
@@ -173,6 +185,8 @@ class UiPathRuntimeContext(BaseModel):
173
185
  input_file: Optional[str] = None
174
186
  is_eval_run: bool = False
175
187
  log_handler: Optional[logging.Handler] = None
188
+ chat_handler: Optional[UiPathConversationHandler] = None
189
+
176
190
  model_config = {"arbitrary_types_allowed": True}
177
191
 
178
192
  @classmethod
uipath/_cli/cli_dev.py CHANGED
@@ -1,23 +1,47 @@
1
1
  import asyncio
2
+ import os
2
3
  from typing import Optional
3
4
 
4
5
  import click
5
6
 
6
- from ..telemetry import track
7
- from ._dev._terminal import UiPathDevTerminal
8
- from ._runtime._contracts import UiPathRuntimeContext, UiPathRuntimeFactory
9
- from ._runtime._runtime import UiPathRuntime
10
- from ._utils._console import ConsoleLogger
11
- from .middlewares import Middlewares
7
+ from uipath._cli._dev._terminal import UiPathDevTerminal
8
+ from uipath._cli._runtime._contracts import UiPathRuntimeContext, UiPathRuntimeFactory
9
+ from uipath._cli._runtime._runtime import UiPathRuntime
10
+ from uipath._cli._utils._console import ConsoleLogger
11
+ from uipath._cli._utils._debug import setup_debugging
12
+ from uipath._cli.cli_init import init # type: ignore[attr-defined]
13
+ from uipath._cli.middlewares import Middlewares
14
+ from uipath.telemetry import track
12
15
 
13
16
  console = ConsoleLogger()
14
17
 
15
18
 
16
19
  @click.command()
17
20
  @click.argument("interface", default="terminal")
21
+ @click.option(
22
+ "--debug",
23
+ is_flag=True,
24
+ help="Enable debugging with debugpy. The process will wait for a debugger to attach.",
25
+ )
26
+ @click.option(
27
+ "--debug-port",
28
+ type=int,
29
+ default=5678,
30
+ help="Port for the debug server (default: 5678)",
31
+ )
18
32
  @track
19
- def dev(interface: Optional[str]) -> None:
33
+ def dev(interface: Optional[str], debug: bool, debug_port: int) -> None:
20
34
  """Launch interactive debugging interface."""
35
+ project_file = os.path.join(os.getcwd(), "uipath.json")
36
+
37
+ if not os.path.exists(project_file):
38
+ console.warning("Project not initialized. Running `uipath init`...")
39
+ ctx = click.get_current_context()
40
+ ctx.invoke(init)
41
+
42
+ if not setup_debugging(debug, debug_port):
43
+ console.error(f"Failed to start debug server on port {debug_port}")
44
+
21
45
  console.info("Launching UiPath debugging terminal ...")
22
46
  result = Middlewares.next(
23
47
  "dev",
uipath/_cli/cli_init.py CHANGED
@@ -90,7 +90,9 @@ def get_user_script(directory: str, entrypoint: Optional[str] = None) -> Optiona
90
90
  python_files = [f for f in os.listdir(directory) if f.endswith(".py")]
91
91
 
92
92
  if not python_files:
93
- console.error("No python files found in the current directory.")
93
+ console.error(
94
+ "No python files found in the current directory.\nPlease specify the entrypoint: `uipath init <entrypoint_path>`"
95
+ )
94
96
  return None
95
97
  elif len(python_files) == 1:
96
98
  return os.path.join(directory, python_files[0])
@@ -0,0 +1,135 @@
1
+ """UiPath Conversation Models.
2
+
3
+ This module provides Pydantic models that represent the JSON event schema for conversations between a client (UI) and an LLM/agent.
4
+
5
+ The event objects define a hierarchal conversation structure:
6
+
7
+ * Conversation
8
+ * Exchange
9
+ * Message
10
+ * Content Parts
11
+ * Citations
12
+ * Tool Calls
13
+ * Tool Results
14
+
15
+ A conversation may contain multiple exchanges, and an exchange may contain multiple messages. A message may contain
16
+ multiple content parts, each of which can be text or binary, including media input and output streams; and each
17
+ content part can include multiple citations. A message may also contain multiple tool calls, which may contain a tool
18
+ result.
19
+
20
+ The protocol also supports a top level, "async", input media streams (audio and video), which can span multiple
21
+ exchanges. These are used for Gemini's automatic turn detection mode, where the LLM determines when the user has
22
+ stopped talking and starts producing output. The output forms one or more messages in an exchange with no explicit
23
+ input message. However, the LLM may produce an input transcript which can be used to construct the implicit input
24
+ message that started the exchange.
25
+
26
+ In addition, the protocol also supports "async" tool calls that span multiple exchanges. This can be used with
27
+ Gemini's asynchronous function calling protocol, which allows function calls to produce results that interrupt the
28
+ conversation when ready, even after multiple exchanges. They also support generating multiple results from a single
29
+ tool call. By contrast most tool calls are scoped to a single message, which contains both the call and the single
30
+ result produced by that call.
31
+
32
+ Not all features supported by the protocol will be supported by all clients and LLMs. The optional top level
33
+ `capabilities` property can be used to communicate information about supported features. This property should be set
34
+ on the first event written to a new websocket connection. This initial event may or may not contain additional
35
+ sub-events.
36
+ """
37
+
38
+ from .async_stream import (
39
+ UiPathConversationAsyncInputStreamEndEvent,
40
+ UiPathConversationAsyncInputStreamEvent,
41
+ UiPathConversationAsyncInputStreamStartEvent,
42
+ UiPathConversationInputStreamChunkEvent,
43
+ )
44
+ from .citation import (
45
+ UiPathConversationCitationEndEvent,
46
+ UiPathConversationCitationEvent,
47
+ UiPathConversationCitationSource,
48
+ UiPathConversationCitationSourceMedia,
49
+ UiPathConversationCitationSourceUrl,
50
+ UiPathConversationCitationStartEvent,
51
+ )
52
+ from .content import (
53
+ UiPathConversationContentPart,
54
+ UiPathConversationContentPartChunkEvent,
55
+ UiPathConversationContentPartEndEvent,
56
+ UiPathConversationContentPartEvent,
57
+ UiPathConversationContentPartStartEvent,
58
+ UiPathExternalValue,
59
+ UiPathInlineValue,
60
+ )
61
+ from .conversation import (
62
+ UiPathConversationCapabilities,
63
+ UiPathConversationEndEvent,
64
+ UiPathConversationStartedEvent,
65
+ UiPathConversationStartEvent,
66
+ )
67
+ from .event import UiPathConversationEvent
68
+ from .exchange import (
69
+ UiPathConversationExchange,
70
+ UiPathConversationExchangeEndEvent,
71
+ UiPathConversationExchangeEvent,
72
+ UiPathConversationExchangeStartEvent,
73
+ )
74
+ from .message import (
75
+ UiPathConversationMessage,
76
+ UiPathConversationMessageEndEvent,
77
+ UiPathConversationMessageEvent,
78
+ UiPathConversationMessageStartEvent,
79
+ )
80
+ from .meta import UiPathConversationMetaEvent
81
+ from .tool import (
82
+ UiPathConversationToolCall,
83
+ UiPathConversationToolCallEndEvent,
84
+ UiPathConversationToolCallEvent,
85
+ UiPathConversationToolCallResult,
86
+ UiPathConversationToolCallStartEvent,
87
+ )
88
+
89
+ __all__ = [
90
+ # Root
91
+ "UiPathConversationEvent",
92
+ # Conversation
93
+ "UiPathConversationCapabilities",
94
+ "UiPathConversationStartEvent",
95
+ "UiPathConversationStartedEvent",
96
+ "UiPathConversationEndEvent",
97
+ # Exchange
98
+ "UiPathConversationExchangeStartEvent",
99
+ "UiPathConversationExchangeEndEvent",
100
+ "UiPathConversationExchangeEvent",
101
+ "UiPathConversationExchange",
102
+ # Message
103
+ "UiPathConversationMessageStartEvent",
104
+ "UiPathConversationMessageEndEvent",
105
+ "UiPathConversationMessageEvent",
106
+ "UiPathConversationMessage",
107
+ # Content
108
+ "UiPathConversationContentPartChunkEvent",
109
+ "UiPathConversationContentPartStartEvent",
110
+ "UiPathConversationContentPartEndEvent",
111
+ "UiPathConversationContentPartEvent",
112
+ "UiPathConversationContentPart",
113
+ "UiPathInlineValue",
114
+ "UiPathExternalValue",
115
+ # Citation
116
+ "UiPathConversationCitationStartEvent",
117
+ "UiPathConversationCitationEndEvent",
118
+ "UiPathConversationCitationEvent",
119
+ "UiPathConversationCitationSource",
120
+ "UiPathConversationCitationSourceUrl",
121
+ "UiPathConversationCitationSourceMedia",
122
+ # Tool
123
+ "UiPathConversationToolCallStartEvent",
124
+ "UiPathConversationToolCallEndEvent",
125
+ "UiPathConversationToolCallEvent",
126
+ "UiPathConversationToolCallResult",
127
+ "UiPathConversationToolCall",
128
+ # Async Stream
129
+ "UiPathConversationInputStreamChunkEvent",
130
+ "UiPathConversationAsyncInputStreamStartEvent",
131
+ "UiPathConversationAsyncInputStreamEndEvent",
132
+ "UiPathConversationAsyncInputStreamEvent",
133
+ # Meta
134
+ "UiPathConversationMetaEvent",
135
+ ]
@@ -0,0 +1,54 @@
1
+ """Async input stream events."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class UiPathConversationInputStreamChunkEvent(BaseModel):
9
+ """Represents a single chunk of input stream data."""
10
+
11
+ input_stream_sequence: Optional[int] = Field(None, alias="inputStreamSequence")
12
+ data: str
13
+
14
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
15
+
16
+
17
+ class UiPathConversationAsyncInputStreamStartEvent(BaseModel):
18
+ """Signals the start of an asynchronous input stream."""
19
+
20
+ mime_type: str = Field(..., alias="mimeType")
21
+ start_of_speech_sensitivity: Optional[str] = Field(
22
+ None, alias="startOfSpeechSensitivity"
23
+ )
24
+ end_of_speech_sensitivity: Optional[str] = Field(
25
+ None, alias="endOfSpeechSensitivity"
26
+ )
27
+ prefix_padding_ms: Optional[int] = Field(None, alias="prefixPaddingMs")
28
+ silence_duration_ms: Optional[int] = Field(None, alias="silenceDurationMs")
29
+ meta_data: Optional[Dict[str, Any]] = Field(None, alias="metaData")
30
+
31
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
32
+
33
+
34
+ class UiPathConversationAsyncInputStreamEndEvent(BaseModel):
35
+ """Signals the end of an asynchronous input stream."""
36
+
37
+ meta_data: Optional[Dict[str, Any]] = Field(None, alias="metaData")
38
+ last_chunk_content_part_sequence: Optional[int] = Field(
39
+ None, alias="lastChunkContentPartSequence"
40
+ )
41
+
42
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
43
+
44
+
45
+ class UiPathConversationAsyncInputStreamEvent(BaseModel):
46
+ """Encapsulates sub-events related to an asynchronous input stream."""
47
+
48
+ stream_id: str = Field(..., alias="streamId")
49
+ start: Optional[UiPathConversationAsyncInputStreamStartEvent] = None
50
+ end: Optional[UiPathConversationAsyncInputStreamEndEvent] = None
51
+ chunk: Optional[UiPathConversationInputStreamChunkEvent] = None
52
+ meta_event: Optional[Dict[str, Any]] = Field(None, alias="metaEvent")
53
+
54
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
@@ -0,0 +1,70 @@
1
+ """Citation events for message content."""
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class UiPathConversationCitationStartEvent(BaseModel):
9
+ """Indicates the start of a citation target in a content part."""
10
+
11
+ pass
12
+
13
+
14
+ class UiPathConversationCitationEndEvent(BaseModel):
15
+ """Indicates the end of a citation target in a content part."""
16
+
17
+ sources: List[Dict[str, Any]]
18
+
19
+
20
+ class UiPathConversationCitationEvent(BaseModel):
21
+ """Encapsulates sub-events related to citations."""
22
+
23
+ citation_id: str = Field(..., alias="citationId")
24
+ start: Optional[UiPathConversationCitationStartEvent] = None
25
+ end: Optional[UiPathConversationCitationEndEvent] = None
26
+
27
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
28
+
29
+
30
+ class UiPathConversationCitationSourceUrl(BaseModel):
31
+ """Represents a citation source that can be rendered as a link (URL)."""
32
+
33
+ url: str
34
+
35
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
36
+
37
+
38
+ class UiPathConversationCitationSourceMedia(BaseModel):
39
+ """Represents a citation source that references media, such as a PDF document."""
40
+
41
+ mime_type: str = Field(..., alias="mimeType")
42
+ download_url: Optional[str] = Field(None, alias="downloadUrl")
43
+ page_number: Optional[str] = Field(None, alias="pageNumber")
44
+
45
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
46
+
47
+
48
+ class UiPathConversationCitationSource(BaseModel):
49
+ """Represents a citation source, either a URL or media reference."""
50
+
51
+ title: Optional[str] = None
52
+
53
+ # Union of Url or Media
54
+ url: Optional[str] = None
55
+ mime_type: Optional[str] = Field(None, alias="mimeType")
56
+ download_url: Optional[str] = Field(None, alias="downloadUrl")
57
+ page_number: Optional[str] = Field(None, alias="pageNumber")
58
+
59
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
60
+
61
+
62
+ class UiPathConversationCitation(BaseModel):
63
+ """Represents a citation or reference inside a content part."""
64
+
65
+ citation_id: str = Field(..., alias="citationId")
66
+ offset: int
67
+ length: int
68
+ sources: List[UiPathConversationCitationSource]
69
+
70
+ model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)