v0.28.0
Added
-
GatewayServer(trace_health_routes=...)/GATEWAY_TRACE_HEALTH_ROUTESto opt/healthzand/readyzout of per-request OTel spans. Both routes previously wrapped every request in agateway.healthz/gateway.readyzspan unconditionally. Under kubelet-style probing every few seconds, each probe becomes its own root trace, which a deployment may prefer to filter at the source rather than downstream in the collector. Defaults toTrue(spans emitted, matching prior behaviour); setting it toFalsedisables span creation for these two routes entirely — response bodies and status codes are unaffected either way. (#76) -
GatewayServer(max_schema_depth=...)/GATEWAY_MAX_SCHEMA_DEPTHto configure the registry-ingest schema-nesting-depth cap. Previously hardcoded at 5, with no deployment-side recourse for a tool whoseinputSchemalegitimately nests deeper. The default stays5. The cap bounds schema complexity for LLM consumers, so raising it is documented as available-but-discouraged — prefer flattening an over-deep upstream schema. Must be an integer in [1, 50]; the upper bound isn't a sanity nicety, since the ingest-time depth/$refrecursion itself becomes a stack-overflow risk on adversarial input well before four-figure depths. An out-of-range or non-integer value is rejected loudly at construction (or env-parse) time as a configuration error, distinct from the per-toolSchemaValidationErrorskip path. (#79)
Changed
- Relaxed schema-depth counting:
Optional[X]/X | Noneno longer costs 2 extra nesting levels. Pydantic (and most JSON-Schema generators) encodeOptional[X]as{"anyOf": [X, {"type": "null"}]}, which the depth counter charged as 3 additional steps versus a requiredXat the same position — so an optional field started 2 levels closer to the cap than a required one of identical real complexity. AnanyOf/oneOfvalue that is exactly a 2-member union with one member being exactly{"type": "null"}now contributes the same depth as its non-null member alone. The null-member test is strict: a null branch additionally carryingtitle/description/etc. keeps the old counting, as do 3+-branch unions and unions without a null member. This is a validator relaxation, not a tightening — aninputSchemapreviously rejected solely becauseOptional[...]wrappers pushed it past the cap is now admitted.$refdetection was updated in lockstep so it can't spuriously fail closed on a wrapper depth the counter no longer charges. (#79)
Fixed
- Bounded the real recursive-call count independently of the transparent-depth counting, closing a
RecursionErrorDoS the relaxation would otherwise have introduced. Because the transparency recurses into a wrapper's non-null member without advancing the logical depth counter, a long chain of{"anyOf": [<inner>, {"type": "null"}]}wrappers around one leaf climbed a real Python stack frame per wrapper while the reported depth stayed low — silently admitting schemas that should have been rejected, and at a few thousand wrappers raising an uncaughtRecursionErrorthat escapes the registry's per-toolSchemaValidationErrorcatch and aborts the whole upstream's registration._schema_depthand_contains_refnow also track an always-advancing physical-recursion counter (cap 100, independent of the logical cap) and reject cleanly well before either threshold. The rejection carries its own reason — a wrapper chain that exhausts the traversal ceiling is reported as such, not as a nesting-depth violation quoting a number that appears nowhere in the schema — while remaining aSchemaValidationErrorsubclass, so the registry's per-tool skip path is unchanged. (#79)
Backward compatible (additive; the depth change only widens what is accepted). Full changelog: CHANGELOG.md