Skip to content
Open
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
6 changes: 5 additions & 1 deletion python/copilot/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,11 @@ def _normalize_result(result: Any) -> ToolResult:
# Everything else gets JSON-serialized (with Pydantic model support)
def default(obj: Any) -> Any:
if isinstance(obj, BaseModel):
return obj.model_dump()
# mode="json" coerces datetime/UUID/Decimal/Enum fields to JSON-native
# types. The default mode="python" leaves them as native objects, which
# json.dumps then passes back into this hook and we reject as
# unserializable -- turning an ordinary tool result into a failure.
return obj.model_dump(mode="json")
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

try:
Expand Down
31 changes: 31 additions & 0 deletions python/test_tools.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Unit tests for define_tool"""

import datetime
import decimal
import enum
import json
import uuid

import pytest
from pydantic import BaseModel, ConfigDict, Field, field_validator
Expand Down Expand Up @@ -389,6 +393,33 @@ class Item(BaseModel):
assert parsed == [{"name": "a", "value": 1}, {"name": "b", "value": 2}]
assert result.result_type == "success"

def test_pydantic_model_with_non_primitive_fields_is_serialized(self):
class IssueState(enum.StrEnum):
OPEN = "open"
CLOSED = "closed"

class Issue(BaseModel):
id: uuid.UUID
created_at: datetime.datetime
price: decimal.Decimal
state: IssueState

issue = Issue(
id=uuid.UUID("6b686a99-0000-4000-8000-000000000000"),
created_at=datetime.datetime(2026, 8, 1, 1, 49, 10),
price=decimal.Decimal("1.5"),
state=IssueState.OPEN,
)
result = _normalize_result(issue)
parsed = json.loads(result.text_result_for_llm)
assert parsed == {
"id": "6b686a99-0000-4000-8000-000000000000",
"created_at": "2026-08-01T01:49:10",
"price": "1.5",
"state": "open",
}
assert result.result_type == "success"

def test_raises_for_unserializable_value(self):
# Functions cannot be JSON serialized
with pytest.raises(TypeError, match="Failed to serialize"):
Expand Down