Skip to content

feat(responses): register operations for request-head classification - #788

Draft
cdoern wants to merge 1 commit into
praxis-proxy:mainfrom
cdoern:feat/responses-operation-registry
Draft

feat(responses): register operations for request-head classification#788
cdoern wants to merge 1 commit into
praxis-proxy:mainfrom
cdoern:feat/responses-operation-registry

Conversation

@cdoern

@cdoern cdoern commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #743

Stacked on #772

The first commit on this branch, edee244, is the #746 work under review in #772.
It is included because this registry is the first consumer of it. Merge #772
first
; this branch will then contain only its own commit. Review just
e09e42d here.

Summary

Responses operation identity came from path string matching in the request
classifier — hand-written prefix and suffix checks per endpoint. This declares
the supported operations in a family-owned registry instead, so identity comes
from method, path, and protocol headers before any payload is read.

Eight operations are registered against the shared registry:

Operation ID Method Path Body
createResponse POST /responses JSON, required
praxis_createResponseWebSocket GET (WebSocket) /responses none
getResponse GET /responses/{response_id} none
deleteResponse DELETE /responses/{response_id} none
cancelResponse POST /responses/{response_id}/cancel none
listInputItems GET /responses/{response_id}/input_items none
Getinputtokencounts POST /responses/input_tokens JSON, required
Compactconversation POST /responses/compact JSON, required

Operation IDs are the official ones from the pinned specification, reproduced
verbatim — including upstream's casing for the last two.

Contract ownership

Praxis proxies the Responses contract rather than owning it, so every operation
declares its runtime request-body shape without an owned OpenAPI contract. This
is the first registry to depend on body shape being independent of contract
ownership, which is the change #746 makes.

WebSocket as a protocol extension

The pinned specification does not represent the Responses WebSocket handshake as
an HTTP operation, so it is declared an explicit Praxis protocol extension and
separated from POST /v1/responses by transport rather than by inspecting a
body. Drift checks skip declared extensions, and fail if one later turns up in
the specification.

Drift check

cargo xtask check-responses-registry, wired into make lint, compares
registered method, path, and operation ID against the pinned specification.

It projects the specification through the existing semantic YAML reader rather
than parsing it directly, because the pinned document contains an integer
(components.schemas.CreateChatCompletionRequest.allOf[1].properties.seed.minimum,
line 32725) that serde_yaml rejects.

Verified to fail correctly: renaming cancelResponse in the registry produces

POST /responses/{response_id}/cancel registers operation ID cancelResponseTypo
but the pinned specification says cancelResponse

and exit code 1.

File placement

The registry lives in responses/routes/mod.rs rather than responses/routes.rs
because the filter doc generator parses non-anchor files in a category root as
shared types. As a plain file it leaked registry type documentation into four
unrelated filter docs, replacing their descriptions with
"Static metadata for one Responses operation".

Testing

  • cargo test --workspace — all suites pass, including 2556 in praxis-ai-apis
  • make lint — passes end to end, including the new drift check
  • make doc — clean

New coverage: every registered operation resolving from its own template, static
endpoints not consumed as response IDs, identifier paths still capturing the
response ID, create and WebSocket separated without reading a body, unsupported
methods and unknown subresources not matching, body-bearing versus bodyless
shapes, registry key and operation ID uniqueness, and the WebSocket operation
being the only declared protocol extension.

Not in scope

Switching the classifier and Responses filters over to consume this registry
depends on the openai_operation classifier in #744.

@praxis-bot praxis-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review

Summary: Registers eight Responses operations in a family-owned registry, moving identity from body heuristics to request-head classification (method, path, transport). Introduces shared infrastructure (RouteParams, match_operation, OperationEntry) and migrates Conversations to use it. Adds a pinned-spec drift check wired into make lint.

Overall: Clean implementation. The shared matcher correctly handles precedence (static segments outrank parameters via max_by_key), transport separation, path normalization, and bounded parameter capture. The Conversations migration preserves the public API while switching to generic name-based parameter lookup. Test coverage is comprehensive: uniqueness, round-trip resolution, precedence, transport separation, capacity limits, and drift detection. No issues found at Critical, Large, or Medium severity.

Severity Count
Critical 0
Large 0
Medium 0

@praxis-bot-app

Copy link
Copy Markdown

Unsigned commits: 7760b51, fd47caf. Please sign your commits.

@cdoern
cdoern force-pushed the feat/responses-operation-registry branch from fd47caf to 94b4e25 Compare August 31, 2026 14:18
Responses operation identity came from path string matching in the request
classifier, with hand-written prefix and suffix checks per endpoint. Declare the
supported operations in a family-owned registry instead, so identity comes from
method, path, and protocol headers before any payload is read.

Register the eight supported operations against the shared operation registry:
create, retrieve, delete, cancel, list input items, count input tokens, compact,
and the WebSocket handshake. Operation IDs are the official ones from the pinned
specification, reproduced verbatim including upstream's casing for
`Getinputtokencounts` and `Compactconversation`.

Praxis proxies the Responses contract rather than owning it, so every operation
declares its runtime request-body shape without an owned OpenAPI contract. This
is the first registry to rely on request-body shape being independent of
contract ownership.

The WebSocket handshake shares its method and path with no HTTP operation the
pinned specification represents, so it is declared as an explicit Praxis
protocol extension and separated from `POST /v1/responses` by transport. Drift
checks skip declared extensions and fail if one later appears in the
specification.

Add `cargo xtask check-responses-registry`, wired into `make lint`, comparing
registered method, path, and operation ID against the pinned specification. The
check projects the specification through the existing semantic YAML reader,
because the pinned document contains an integer that serde_yaml cannot parse.

The registry lives in a subdirectory because non-anchor files in a filter
category root are parsed as shared types by the filter doc generator, which
would otherwise leak registry type documentation into unrelated filter docs.

Closes praxis-proxy#743

Signed-off-by: Charlie Doern <cdoern@redhat.com>
@cdoern
cdoern force-pushed the feat/responses-operation-registry branch from 94b4e25 to e209fa8 Compare September 1, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

feat(responses): register operations for request-head classification

3 participants