Summary
In the Python SDK, a tool handler that returns a Pydantic model (or a plain dict) with a datetime, date, UUID, Decimal, Enum or set field never reaches the model. Instead the model receives:
Invoking this tool produced an error. Detailed information is not available.
This happens on every call, and the tool author gets no indication of what went wrong.
Location
python/copilot/tools.py:352-360, in _normalize_result:
# Everything else gets JSON-serialized (with Pydantic model support)
def default(obj: Any) -> Any:
if isinstance(obj, BaseModel):
return obj.model_dump()
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
try:
json_str = json.dumps(result, default=default)
except (TypeError, ValueError) as exc:
raise TypeError(f"Failed to serialize tool result: {exc}") from exc
Root cause
BaseModel.model_dump() uses the default mode="python", which leaves datetime, date, time, UUID, Decimal, Enum and set as native Python objects. json.dumps then hands those objects back to the same default hook; they are not BaseModel instances, so the hook raises TypeError.
_normalize_result re-raises, and that raise happens inside wrapped_handler's try block (tools.py:268-280), which deliberately converts any exception into the redacted generic failure. So the advertised "Pydantic model support" only holds for models whose fields are all JSON primitives.
Reproduction
import datetime, decimal, uuid
from pydantic import BaseModel
from copilot.tools import _normalize_result
class Issue(BaseModel):
id: uuid.UUID
created_at: datetime.datetime
price: decimal.Decimal
_normalize_result(Issue(
id=uuid.uuid4(),
created_at=datetime.datetime.now(),
price=decimal.Decimal("1.5"),
))
Observed:
TypeError: Failed to serialize tool result: Object of type UUID is not JSON serializable
Through a real @define_tool handler the same input yields:
result_type: failure
text: Invoking this tool produced an error. Detailed information is not available.
error: Failed to serialize tool result: Object of type UUID is not JSON serializable
Impact
Returning a model with a timestamp or an id is ordinary tool design, so this is easy to hit and hard to diagnose — the redaction is intentional for security, but it also hides the fact that the failure is in the SDK's serializer rather than in the user's handler.
The TypeScript SDK is not affected: nodejs/src/session.ts:1074-1079 normalizes with JSON.stringify, and JS Date implements toJSON().
Suggested fix
Use Pydantic's JSON mode so each field is coerced to a JSON-native type before json.dumps sees it:
- return obj.model_dump()
+ return obj.model_dump(mode="json")
Models whose fields are all JSON primitives serialize exactly as before. Happy to send a PR — I have one ready with a regression test.
For fuller parity with the TypeScript SDK you may also want fallbacks in default for dataclasses.asdict, datetime/date/time.isoformat(), UUID/Decimal → str, Enum → .value and set → list, so a plain dict carrying those types works too. I left that out of the PR to keep the change focused.
Summary
In the Python SDK, a tool handler that returns a Pydantic model (or a plain
dict) with adatetime,date,UUID,Decimal,Enumorsetfield never reaches the model. Instead the model receives:This happens on every call, and the tool author gets no indication of what went wrong.
Location
python/copilot/tools.py:352-360, in_normalize_result:Root cause
BaseModel.model_dump()uses the defaultmode="python", which leavesdatetime,date,time,UUID,Decimal,Enumandsetas native Python objects.json.dumpsthen hands those objects back to the samedefaulthook; they are notBaseModelinstances, so the hook raisesTypeError._normalize_resultre-raises, and that raise happens insidewrapped_handler'stryblock (tools.py:268-280), which deliberately converts any exception into the redacted generic failure. So the advertised "Pydantic model support" only holds for models whose fields are all JSON primitives.Reproduction
Observed:
Through a real
@define_toolhandler the same input yields:Impact
Returning a model with a timestamp or an id is ordinary tool design, so this is easy to hit and hard to diagnose — the redaction is intentional for security, but it also hides the fact that the failure is in the SDK's serializer rather than in the user's handler.
The TypeScript SDK is not affected:
nodejs/src/session.ts:1074-1079normalizes withJSON.stringify, and JSDateimplementstoJSON().Suggested fix
Use Pydantic's JSON mode so each field is coerced to a JSON-native type before
json.dumpssees it:Models whose fields are all JSON primitives serialize exactly as before. Happy to send a PR — I have one ready with a regression test.
For fuller parity with the TypeScript SDK you may also want fallbacks in
defaultfordataclasses.asdict,datetime/date/time.isoformat(),UUID/Decimal→str,Enum→.valueandset→list, so a plaindictcarrying those types works too. I left that out of the PR to keep the change focused.