Skip to content
Closed
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
15 changes: 15 additions & 0 deletions python/copilot/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@

from __future__ import annotations

import dataclasses
import datetime
import decimal
import enum
import inspect
import json
import uuid
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Literal, TypeVar, get_type_hints, overload
Expand Down Expand Up @@ -352,6 +357,16 @@ def _normalize_result(result: Any) -> ToolResult:
def default(obj: Any) -> Any:
if isinstance(obj, BaseModel):
return obj.model_dump(mode="json")
if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
return obj.isoformat()
if isinstance(obj, (uuid.UUID, decimal.Decimal)):
return str(obj)
if isinstance(obj, enum.Enum):
return obj.value
if isinstance(obj, (set, frozenset)):
return list(obj)
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
return dataclasses.asdict(obj)
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

try:
Expand Down
39 changes: 39 additions & 0 deletions python/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,45 @@ def test_raises_for_unserializable_value(self):
with pytest.raises(TypeError, match="Failed to serialize"):
_normalize_result(lambda x: x)

def test_dict_with_non_primitive_values_is_serialized(self):
"""Plain dicts containing datetime/UUID/Decimal/Enum/set serialize correctly."""
from dataclasses import dataclass
from datetime import date, datetime, time
from decimal import Decimal
from enum import Enum
from uuid import UUID

class Color(Enum):
RED = "red"

@dataclass
class Point:
x: int
y: int

result = _normalize_result({
"id": UUID("12345678-1234-5678-1234-567812345678"),
"created": datetime(2026, 1, 15, 10, 30, 0),
"day": date(2026, 1, 15),
"meeting_time": time(14, 30),
"score": Decimal("99.5"),
"color": Color.RED,
"tags": {"a", "b"},
"frozen": frozenset([1, 2]),
"point": Point(x=1, y=2),
})
parsed = json.loads(result.text_result_for_llm)
assert parsed["id"] == "12345678-1234-5678-1234-567812345678"
assert parsed["created"] == "2026-01-15T10:30:00"
assert parsed["day"] == "2026-01-15"
assert parsed["meeting_time"] == "14:30:00"
assert parsed["score"] == "99.5"
assert parsed["color"] == "red"
assert set(parsed["tags"]) == {"a", "b"}
assert set(parsed["frozen"]) == {1, 2}
assert parsed["point"] == {"x": 1, "y": 2}
assert result.result_type == "success"


class TestConvertMcpCallToolResult:
def test_text_only_call_tool_result(self):
Expand Down