Skip to content

Parse request content

The public egress_gate.request_content package lets built-in and custom gates interpret the current request body as independently inspectable text targets. It supports a complete UTF-8 body, selected strings in a strict JSON document, and normalized message blocks derived from harness-specific JSON envelopes.

Utf8TextParser, JsonFieldsParser, and MessageBlocksParser implement the same RequestContentParser contract. Each parser owns both text extraction and rendering immutable TextReplacement values back into a complete replacement body. The regex gate composes these parsers, but they are not regex-specific; trusted custom gates can use the same public API.

Here, bounded means that parsing and replacement enforce explicit limits on body size, JSON depth, node and selector counts, selected text, and message blocks. All work also uses the request's shared deadline. See Limits and failures for the failure contract.

Use a parser from a custom gate

Prepare a parser once with the gate, then parse the current body inside each evaluation. The returned view contains request-local targets and owns rendering their replacements back into complete body bytes:

Parse and replace selected JSON text
from egress_gate.request_content import (
    JsonFieldsParser,
    JsonKeySegment,
    JsonSelector,
    TextReplacement,
)

parser = JsonFieldsParser(
    selectors=(
        JsonSelector(
            segments=(JsonKeySegment(kind="key", value="prompt"),),
        ),
    ),
)

parsed = parser.parse(request.body, timeout=timeout)
replacements = tuple(
    TextReplacement(target_id=target.id, text=transform(target.text))
    for target in parsed.targets
)
replacement_body = parsed.replace_text(replacements, timeout=timeout)

The custom gate can return replacement_body through RequestMutations after declaring GateCapability.REPLACE_BODY. Keeping parsing and rendering on the same request-local view prevents a replacement from targeting text that the parser did not expose.

Select JSON values with typed paths

A JsonSelector is an ordered sequence of typed path segments. It is not a JSONPath string. Egress Gate starts with one JSON value and applies each segment to the values selected by the preceding segment.

Segment Required input Selection
key JSON object The value of the member whose key exactly equals value
index JSON array The item at position value; 0 is the first item, 1 is the second, and so on
each JSON object or array Every immediate object-member value or array item

For example, this selector chooses the content value from every item in the top-level messages array:

Select every message's content
segments:
  - kind: key
    value: messages
  - kind: each
  - kind: key
    value: content

Given this document:

{
  "messages": [
    {"content": "first"},
    {"content": "second"}
  ]
}

the selector produces the two string values first and second.

Use index when a policy needs one specific array position. The numeric value is the position in the array:

Select the first message's content
segments:
  - kind: key
    value: messages
  - kind: index
    value: 0
  - kind: key
    value: content

This selector produces only first. An index equal to or greater than the array length selects nothing; it is not an input error.

A segment applied to the wrong JSON kind also selects nothing. For example, key does not select from an array, and index does not select from an object. Missing paths therefore produce no selected values. When a parser requests text, selected non-string terminal values also produce no text targets.

Multiple selectors are evaluated in configuration order. When selectors reach the same JSON node, Egress Gate returns that node once. Results produced by one selector follow their order in the document.

Select JSON string fields

JsonFieldsParser evaluates selectors from the JSON document root and exposes each selected string as an independent TextTarget. A match or other custom gate operation on one target cannot span another selected string.

The regex gate exposes this parser through scan.kind: json-fields:

Scan selected JSON strings
scan:
  kind: json-fields
  selectors:
    - segments:
        - kind: key
          value: messages
        - kind: each
        - kind: key
          value: content
  action:
    kind: replace
    template: '[{entity}]'

Map normalized message blocks

MessageBlocksParser parses the request body as the same strict JSON document, then delegates message normalization to a MessageBlockExtractor. The built-in JsonMessageBlockExtractor applies a JsonMessageMapConfig for ordinary JSON harness envelopes.

The mapping's messages selector starts at the document root. Its text_selectors, tool_input_selectors, and tool_output_selectors start at each selected message object. This distinction is important: relative text selectors do not repeat the path to the message array.

Message roles normalize to system, developer, user, assistant, tool, or unknown. Blocks normalize to text, tool_input, or tool_output. Provider- or harness-specific adapters can implement the public MessageBlockExtractor protocol without changing request-content consumers such as the regex gate.

When selector groups reach the same JSON string, the extractor retains each distinct block classification. MessageBlocksParser applies role and block kind filters first, then exposes the shared string once. A broad text_selectors entry therefore cannot hide the same node from an explicit tool-input or tool-output filter.

See the regex gate for a complete policy example and custom gates for the trusted extension contract.

Replacement and parsing behavior

JSON parsing is strict and bounded. It rejects invalid UTF-8, malformed JSON, duplicate object keys, non-standard constants, invalid Unicode scalar values, and configured parsing limits.

Structured replacement re-encodes only selected JSON string tokens. It preserves every source byte outside those tokens, including whitespace, number spellings, key order, and escaping in unrelated strings. A parsed-content view rejects replacement of targets it did not expose.

Request-content parsers are prepared stateless objects. Each parsed result and its document-local target identities belong to one request evaluation and must not be reused with another request body.