Skip to content
Merged
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
2 changes: 1 addition & 1 deletion plugins/communication_protocols/http/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "utcp-http"
version = "1.1.10"
version = "1.1.11"
authors = [
{ name = "UTCP Contributors" },
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,13 +401,21 @@ def _merge_examples(self, *objs: Optional[Dict[str, Any]]) -> Optional[List[Any]
Used to combine examples that can appear at more than one level for the
same value, e.g. a Media Type Object and the Schema Object beneath it.
Returns a list suitable for the JSON Schema 'examples' keyword, or None.

De-duplication uses a canonical JSON serialization (sorted keys) as the
identity. This is order-insensitive for objects and type-aware, so it
does not collapse semantically distinct examples the way Python's ``==``
would (``True == 1``, ``False == 0``, ``1 == 1.0``).
"""
merged: List[Any] = []
seen: set = set()
for obj in objs:
if not isinstance(obj, dict):
continue
for ex in self._extract_examples(obj) or []:
if ex not in merged:
key = json.dumps(ex, sort_keys=True, default=str)
if key not in seen:
seen.add(key)
merged.append(ex)
return merged or None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,56 @@ def test_openapi_converter_array_form_schema_examples():
assert body_param.model_dump(by_alias=True).get("examples") == [{"name": "Gadget A"}, {"name": "Gadget B"}]

assert tool.outputs.examples == ["ok", "done"]


def test_openapi_converter_example_dedup_is_type_aware_and_order_insensitive():
"""De-dup keeps distinct JSON types (true vs 1) and collapses key-reordered objects."""
openapi_spec = {
"openapi": "3.0.0",
"info": {"title": "Test API", "version": "1.0.0"},
"paths": {
"/items": {
"post": {
"operationId": "createItem",
"requestBody": {
"content": {
"application/json": {
# media-type example with one key order...
"examples": {"e1": {"value": {"a": 1, "b": 2}}},
"schema": {
"type": "object",
# ...schema example with the other key order -> collapses to one
"examples": [{"b": 2, "a": 1}],
},
}
}
},
"responses": {
"200": {
"description": "ok",
"content": {
"application/json": {
"schema": {
"type": "object",
# true and 1 are distinct; duplicate true removed
"examples": [True, 1, True],
}
}
},
}
},
}
}
},
}

converter = OpenApiConverter(openapi_spec)
manual = converter.convert()

tool = next((t for t in manual.tools if t.name == "createItem"), None)
assert tool is not None

# {a:1,b:2} and {b:2,a:1} are the same example -> collapsed to one
assert tool.inputs.properties.get("body").examples == [{"a": 1, "b": 2}]
# True vs 1 kept distinct (== would have collapsed them); duplicate True removed
assert tool.outputs.examples == [True, 1]
Loading