Skip to content

v0.28.0

Choose a tag to compare

@namabile namabile released this 12 Aug 03:48
· 6 commits to main since this release
48d9f0c

Added

  • GatewayServer(trace_health_routes=...) / GATEWAY_TRACE_HEALTH_ROUTES to opt /healthz and /readyz out of per-request OTel spans. Both routes previously wrapped every request in a gateway.healthz / gateway.readyz span 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 to True (spans emitted, matching prior behaviour); setting it to False disables span creation for these two routes entirely — response bodies and status codes are unaffected either way. (#76)

  • GatewayServer(max_schema_depth=...) / GATEWAY_MAX_SCHEMA_DEPTH to configure the registry-ingest schema-nesting-depth cap. Previously hardcoded at 5, with no deployment-side recourse for a tool whose inputSchema legitimately nests deeper. The default stays 5. 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/$ref recursion 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-tool SchemaValidationError skip path. (#79)

Changed

  • Relaxed schema-depth counting: Optional[X] / X | None no longer costs 2 extra nesting levels. Pydantic (and most JSON-Schema generators) encode Optional[X] as {"anyOf": [X, {"type": "null"}]}, which the depth counter charged as 3 additional steps versus a required X at the same position — so an optional field started 2 levels closer to the cap than a required one of identical real complexity. An anyOf/oneOf value 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 carrying title/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 — an inputSchema previously rejected solely because Optional[...] wrappers pushed it past the cap is now admitted. $ref detection 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 RecursionError DoS 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 uncaught RecursionError that escapes the registry's per-tool SchemaValidationError catch and aborts the whole upstream's registration. _schema_depth and _contains_ref now 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 a SchemaValidationError subclass, 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