Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/uipath-core/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-core"
version = "0.5.15"
version = "0.5.16"
description = "UiPath Core abstractions"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.11"
Expand Down
13 changes: 8 additions & 5 deletions packages/uipath-core/src/uipath/core/chat/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import uuid
from typing import Any, Sequence

from pydantic import BaseModel, ConfigDict, Field
Expand Down Expand Up @@ -95,7 +96,7 @@ class UiPathConversationContentPartData(BaseModel):

mime_type: str = Field(..., alias="mimeType")
data: InlineOrExternal
citations: Sequence[UiPathConversationCitationData]
citations: Sequence[UiPathConversationCitationData] = Field(default_factory=list)
is_transcript: bool | None = Field(None, alias="isTranscript")
is_incomplete: bool | None = Field(None, alias="isIncomplete")
name: str | None = None
Expand All @@ -106,11 +107,13 @@ class UiPathConversationContentPartData(BaseModel):
class UiPathConversationContentPart(UiPathConversationContentPartData):
"""Represents a single part of message content."""

content_part_id: str = Field(..., alias="contentPartId")
created_at: str = Field(..., alias="createdAt")
updated_at: str = Field(..., alias="updatedAt")
content_part_id: str = Field(
default_factory=lambda: str(uuid.uuid4()), alias="contentPartId"
)
created_at: str | None = Field(None, alias="createdAt")
updated_at: str | None = Field(None, alias="updatedAt")

# Override to use full type
citations: Sequence[UiPathConversationCitation]
citations: Sequence[UiPathConversationCitation] = Field(default_factory=list)

model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
17 changes: 12 additions & 5 deletions packages/uipath-core/src/uipath/core/chat/message.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Message-level events."""

import uuid
from typing import Any, Sequence

from pydantic import BaseModel, ConfigDict, Field
Expand Down Expand Up @@ -61,23 +62,29 @@ class UiPathConversationMessageData(BaseModel):
content_parts: Sequence[UiPathConversationContentPartData] = Field(
..., alias="contentParts"
)
tool_calls: Sequence[UiPathConversationToolCallData] = Field(..., alias="toolCalls")
tool_calls: Sequence[UiPathConversationToolCallData] = Field(
default_factory=list, alias="toolCalls"
)

model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)


class UiPathConversationMessage(UiPathConversationMessageData):
"""Represents a single message within an exchange."""

message_id: str = Field(..., alias="messageId")
created_at: str = Field(..., alias="createdAt")
updated_at: str = Field(..., alias="updatedAt")
message_id: str = Field(
default_factory=lambda: str(uuid.uuid4()), alias="messageId"
)
created_at: str | None = Field(None, alias="createdAt")
updated_at: str | None = Field(None, alias="updatedAt")
span_id: str | None = Field(None, alias="spanId")

# Overrides to use full types
content_parts: Sequence[UiPathConversationContentPart] = Field(
..., alias="contentParts"
)
tool_calls: Sequence[UiPathConversationToolCall] = Field(..., alias="toolCalls")
tool_calls: Sequence[UiPathConversationToolCall] = Field(
default_factory=list, alias="toolCalls"
)

model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)
Empty file.
59 changes: 59 additions & 0 deletions packages/uipath-core/tests/chat/test_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Tests for `UiPathConversationMessage` input validation.

Conversational Agent Service contract treats `role` + `contentParts` as
the load-bearing fields for an inbound user message. `messageId` and
`contentPartId` are GUIDs that identify entities in the conversation
hierarchy; when omitted on input, the model fills them with fresh
UUIDs (matching what `uipath dev` does server-side). `createdAt`,
`updatedAt`, `spanId`, and `toolCalls` are server-allocated and absent
from client input.

These tests pin that behavior so `--input-file` payloads from
`uip codedagent run` validate against the model without requiring
callers to hand-generate UUIDs.
"""

from __future__ import annotations

from uipath.core.chat import UiPathConversationMessage


def test_minimal_user_message_validates_and_fills_ids() -> None:
msg = UiPathConversationMessage.model_validate(
{
"role": "user",
"contentParts": [
{
"mimeType": "text/plain",
"data": {"inline": "hello world"},
}
],
}
)
assert msg.role == "user"
assert msg.tool_calls == []
assert msg.created_at is None
assert msg.updated_at is None
assert msg.message_id # auto-generated UUID
assert msg.content_parts[0].content_part_id # auto-generated UUID
assert msg.content_parts[0].citations == []


def test_explicit_ids_are_preserved() -> None:
msg = UiPathConversationMessage.model_validate(
{
"messageId": "00000000-0000-0000-0000-000000000001",
"role": "user",
"contentParts": [
{
"contentPartId": "00000000-0000-0000-0000-000000000002",
"mimeType": "text/plain",
"data": {"inline": "hello world"},
}
],
}
)
assert msg.message_id == "00000000-0000-0000-0000-000000000001"
assert (
msg.content_parts[0].content_part_id == "00000000-0000-0000-0000-000000000002"
)
4 changes: 2 additions & 2 deletions packages/uipath-core/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/uipath-platform/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/uipath/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading