Priority: Critical
Related: #199
Philosophy
Celeste is a primitives layer. Dict tools are passed through to the provider as-is by design — celeste does not validate tool schemas, names, or structure beyond what it needs for its own dispatch logic. The provider API is the source of truth for what constitutes a valid tool definition. This is the same principle as extra_body: celeste forwards, the provider validates.
This means {"random": "garbage"} reaching the provider and getting rejected there is correct behavior. Celeste should not gate-keep provider-specific tool formats it doesn't know about.
Actual bug
The real issue is that non-dict, non-Tool items (e.g., 42, "string") are silently dropped from the tools list. The for-loop's if/elif chain has no else clause — items that don't match any branch are skipped without error. The user thinks they passed a tool, but it vanishes.
for item in validated_value:
if isinstance(item, Tool):
...
elif isinstance(item, dict) and "name" in item:
...
elif isinstance(item, dict):
...
# ← non-dict items silently ignored here
Fix
Add an else clause raising TypeError for items that are neither Tool instances nor dicts:
else:
msg = f"Expected Tool instance or dict, got {type(item).__name__}"
raise TypeError(msg)
Apply to all 4 ToolsMapper.map() implementations:
src/celeste/protocols/chatcompletions/parameters.py
src/celeste/protocols/openresponses/parameters.py
src/celeste/providers/anthropic/messages/parameters.py
src/celeste/providers/google/generate_content/parameters.py
Priority: Critical
Related: #199
Philosophy
Celeste is a primitives layer. Dict tools are passed through to the provider as-is by design — celeste does not validate tool schemas, names, or structure beyond what it needs for its own dispatch logic. The provider API is the source of truth for what constitutes a valid tool definition. This is the same principle as
extra_body: celeste forwards, the provider validates.This means
{"random": "garbage"}reaching the provider and getting rejected there is correct behavior. Celeste should not gate-keep provider-specific tool formats it doesn't know about.Actual bug
The real issue is that non-dict, non-Tool items (e.g.,
42,"string") are silently dropped from the tools list. The for-loop's if/elif chain has noelseclause — items that don't match any branch are skipped without error. The user thinks they passed a tool, but it vanishes.Fix
Add an
elseclause raisingTypeErrorfor items that are neitherToolinstances nor dicts:Apply to all 4 ToolsMapper.map() implementations:
src/celeste/protocols/chatcompletions/parameters.pysrc/celeste/protocols/openresponses/parameters.pysrc/celeste/providers/anthropic/messages/parameters.pysrc/celeste/providers/google/generate_content/parameters.py