Skip to content

Rewrite rules and protocol overrides

Add rules from a provider’s rewrite page, choosing a type and entering its settings. New providers receive a bound default rule set with the same name. Use advanced bindings when several providers need to share rules.

Console typePurposeAPI action
System textPrepend or append text to system instructionssystem_text
Cache breakpointAdd protocol-specific global, system, message, or tool cache markerscache_breakpoint
JSON rewriteSet or delete a field, or merge an objectset, delete, merge
Text transformApply a regular expression to body text, headers, or query valuesreplace
HeaderSet a value or merge comma-separated valuesheader_set, header_merge

System text supports Claude, OpenAI Chat, Responses (including WebSocket), and Gemini. Cache breakpoints support Claude and OpenAI formats; see Prompt caching for positions and TTLs.

When creating or editing a rule within a rule set, select Advanced JSON to paste one v4 rule object. This is also available on a provider’s rewrite page. Switch between the form and JSON, or format the JSON. Invalid input stays in the editor with an error; saving also checks that the rule can execute.

For example, replace "type":"function"} in the body with "type":"function","strict":false}:

{
"action": "replace",
"phase": "request",
"target": "body",
"pattern": "\"type\":\"function\"\\}",
"replacement": "\"type\":\"function\",\"strict\":false}"
}

Use the v4 action, pattern, and replacement fields. The page manages the rule set, ID, and order, so omit those from the JSON. Optional filters and enabled can be included. Omitted filters impose no restriction, and enabled defaults to true. Each edit accepts one rule object, not an array.

Requests are converted to the upstream protocol, rewritten, and then sent by the channel. Responses are rewritten before conversion back to the client protocol. Rule paths must therefore use the upstream format.

For example, an OpenAI Chat request routed to Claude uses Claude’s system and messages fields by the time request rules run.

Enabled rule-set bindings and the rules within each set run in sortOrder, then ID order. Each rule sees the previous rule’s output. System text and cache breakpoint rules follow the same ordering.

JSON edits require paths such as temperature or messages.*.content. A * segment selects all elements. set requires a valid JSON value, merge requires a JSON object, and delete needs no value.

Text transforms use Rust regular expressions, including (?i), (?s), $1, and ${name} replacements. With body paths, they replace selected JSON string values. Without paths, they operate on the full body text; take care to preserve valid JSON.

Header and query rules require targetName. header_set replaces the header value. header_merge merges comma-separated values and removes duplicates. Avoid storing upstream secrets in shared rules.

FilterMatches
Operation and protocolThe converted operation executed on the provider
ModelEither the upstream or requested model name, with * and ? wildcards
Request headersA regular expression over inbound headers
Stream eventAn SSE event name or JSON type, for body rules

All supplied filters must match. Omitted filters add no restriction. Scope client-specific compatibility rules by request headers so they do not affect other clients.

Model suggestions contain only saved provider models and variants. A provider’s page lists that provider’s models; the standalone rule-set page combines models from its attached providers. An unattached set has no suggestions, but you can still enter a model name or a pattern such as gpt-*. Suggestions do not fetch upstream model directories.

Body rewrites operate on complete events, such as an SSE event or JSON array element, rather than network chunks. An event exceeding settings.maxStreamEventBytes fails. Use an event filter to target particular event types.

This example replaces widget with gadget in selected strings. Substitute your actual rule-set ID.

Terminal window
curl -sS -X POST http://127.0.0.1:8787/admin/api/rules \
-H "Authorization: Bearer $GPROXY_KEY" \
-H 'Content-Type: application/json' \
-d '{"ruleSetId":"your-rule-set-id","action":"replace",
"phase":"request","target":"body","paths":["messages.*.content"],
"pattern":"(?i)\\bwidget\\b","replacement":"gadget",
"filterOperationKeys":[{"operation":"generate_content","dialect":"openai_chat"}]}'

PUT /admin/api/rule-sets/{id}/rules replaces a complete rule list. For system text and cache breakpoints, replacement contains a JSON-encoded configuration string. The console constructs it for you.

GET /admin/api/rule-presets returns the built-in OpenCode, the agent-mono, Aider, Cline, Continue, and Cursor presets. Query the endpoint for the current rules.

POST /admin/api/rule-sets/{id}/rule-presets/{preset} replaces the set’s existing rules. To retain existing rules, export or read them first and save a merged list.

A provider’s routing rules select an upstream protocol. Model routes select providers; these are separate settings.

  • operation_rules overrides a provider’s protocol list for an operation. A supported client protocol passes through; otherwise, conversion targets the first protocol in the list.
  • operation_endpoints overrides the full endpoint URL for an operation, protocol, and transport.

For example, change the protocols for generation:

Terminal window
curl -sS -X POST http://127.0.0.1:8787/admin/api/operation-rules \
-H "Authorization: Bearer $GPROXY_KEY" \
-H 'Content-Type: application/json' \
-d '{"providerId":"your-provider-id","operation":"generate_content",
"action":"dialects","target":["claude","openai_chat"]}'

Without overrides, channel defaults apply. POST /admin/api/providers/{id}/routing-defaults/reset clears the provider’s operation and endpoint overrides.

The custom channel also requires a dialects configuration, for example ["openai_chat", "openai"]. If an operation fails, check upstream support, this list, and operation overrides.

Add filterBody and/or filterHeader to gate an existing action. Both conditions and all other configured filters must match.

{
"action": "set",
"phase": "request",
"target": "body",
"paths": [
"service_tier"
],
"pattern": "",
"replacement": "\"priority\"",
"filterBody": {
"path": "reasoning.effort",
"op": "eq",
"value": "high"
},
"filterHeader": {
"name": "x-client-mode",
"op": "eq",
"value": "fast"
}
}

filterBody reads the current JSON payload after preceding rules. It is supported only for body targets. Paths accept dot-separated keys and array indexes, without wildcards. Equality preserves JSON types; missing differs from null. Invalid JSON never matches a body condition, including not_exists.

filterHeader reads original inbound request headers for any target and phase. Header names are case-insensitive; values are exact, case-sensitive strings without comma splitting. With repeated headers, eq matches any equal value; ne requires a present header with no equal value.

Operators are eq, ne, exists, and not_exists. Equality/inequality require value; existence operators reject it. A missing field does not satisfy ne. Conditions run per complete body, SSE event, NDJSON record, JSON-array element or WebSocket message, without cross-message state. Clear a condition with null; omitted PATCH fields retain existing conditions.

filterBody also accepts a JMESPath expression string. It evaluates the current body, and only boolean true runs the action. Type errors, unknown functions, other evaluation errors and non-boolean results do not match. Syntax is validated on save. The existing { "path", "op", "value" } object form remains supported.

This rule sets max_tokens to 0 only when a text block in the last message contains the keepalive marker. Earlier messages cannot trigger it:

{
"action": "set",
"phase": "request",
"target": "body",
"paths": [
"max_tokens"
],
"replacement": "0",
"filterBody": "length(messages[-1].content[?type(text) == 'string' && contains(text, '[cache-keepalive]')]) > `0`"
}

Negative indexes, projections, contains(), comparisons and boolean composition follow the JMESPath specification. Missing fields evaluate to null and may be explicitly tested; non-JSON payloads never match. The action’s paths still use the existing dot-path syntax independently of the expression.

Expressions compile once. An unchanged payload shares one converted JMESPath document; an actual preceding body edit invalidates that snapshot. Rules without expressions do not create this extra representation. Each stream unit is independent. Queries traversing the full document still scale with its size.