Problem
Message.content accepts Pydantic models, and ToolResult inherits that contract through Message. Some request builders currently lower Pydantic content incorrectly when preparing provider payloads.
The important boundary is text message/tool-result lowering, not a generic celeste.tools serialization helper:
- Chat Completions and OpenResponses currently do message lowering from lower protocol
tools.py modules that import Message / ToolResult.
- That lowering belongs in the text modality protocol layer.
- Anthropic, Google, and Cohere already lower messages in text provider clients and should be patched in place.
Use direct Pydantic APIs at the exact boundary:
BaseModel.model_dump_json() for provider fields that require JSON text.
BaseModel.model_dump(mode="json") for provider fields that accept JSON objects.
model_dump(mode="json", serialize_as_any=True, exclude_none=True) when dumping a Message that may contain nested Pydantic content.
Do not add a generic Celeste serialization helper. Serialization remains owned by Pydantic.
Definition of Done
- Chat Completions serializes
ToolResult(content=ToolOutput(...)) as JSON text.
- OpenResponses serializes
ToolResult(content=ToolOutput(...)) as JSON text.
- Anthropic serializes
ToolResult(content=ToolOutput(...)) as JSON text.
- Google emits JSON-compatible object content for Pydantic tool results.
- Cohere preserves nested Pydantic
Message.content instead of dumping it as {}.
- Plain string tool results keep existing behavior.
- Focused tests cover the contract.
Problem
Message.contentaccepts Pydantic models, andToolResultinherits that contract throughMessage. Some request builders currently lower Pydantic content incorrectly when preparing provider payloads.The important boundary is text message/tool-result lowering, not a generic
celeste.toolsserialization helper:tools.pymodules that importMessage/ToolResult.Use direct Pydantic APIs at the exact boundary:
BaseModel.model_dump_json()for provider fields that require JSON text.BaseModel.model_dump(mode="json")for provider fields that accept JSON objects.model_dump(mode="json", serialize_as_any=True, exclude_none=True)when dumping aMessagethat may contain nested Pydantic content.Do not add a generic Celeste serialization helper. Serialization remains owned by Pydantic.
Definition of Done
ToolResult(content=ToolOutput(...))as JSON text.ToolResult(content=ToolOutput(...))as JSON text.ToolResult(content=ToolOutput(...))as JSON text.Message.contentinstead of dumping it as{}.