Fix protocol translation with scripts
Cross-protocol translation is a core gateway capability, but sometimes you need to do things beyond the default translation — branch field handling by client protocol, inject different parameters by group, correct a protocol's dialect differences, etc. This chapter gives several practical before / after slot script examples.
Prerequisite: read Write your first script transform first to understand the basic structure, mounting, and Context API.
The complete script for switching models by Claude Code plan mode has its own page, see Switch model by Claude Code plan mode with a script.
Example 1: multimodal detection + routing switch (before slot)
Detect image input or web access parameters and switch to a multimodal model. Put it in the before slot (request_transform_before):
function transform(body, context) {
const proto = context.sourceProtocol;
let hasImage = false;
if (proto === "OpenAIChatCompletions") {
const msgs = body.messages;
if (Array.isArray(msgs)) {
for (const msg of msgs) {
if (Array.isArray(msg.content) &&
msg.content.some(b => b.type === "image_url")) {
hasImage = true;
break;
}
}
}
} else if (proto === "Anthropic") {
const msgs = body.messages;
if (Array.isArray(msgs)) {
for (const msg of msgs) {
if (Array.isArray(msg.content) &&
msg.content.some(b => b.type === "image")) {
hasImage = true;
break;
}
}
}
} else if (proto === "Google") {
const contents = body.contents;
if (Array.isArray(contents)) {
for (const c of contents) {
if (Array.isArray(c.parts) &&
c.parts.some(p => p.inlineData || p.fileData)) {
hasImage = true;
break;
}
}
}
}
if (hasImage) {
context.useModel("multimodal-model");
}
return body;
}Key points:
- Must be in the before slot: it needs to read the client's original-format image fields, and different protocols have different field names (OpenAI
image_url, Anthropicimage, GoogleinlineData/fileData) - Branch by
context.sourceProtocol, otherwise cross-protocol clients are misdetected context.useModeldoes a full routing switch (base_url, protocol, credentials all change together)
Image/web-access routing: prefer native switch-route (not a script)
Example 1's before-slot script can detect images by protocol branch, suitable for cross-field computation / complex decisions. But if the decision is only a structural check like "does the request have an image block / is web search enabled", use the native request_payload switch-route rule instead of writing a script: it does not read or write body content, works same-protocol and cross-protocol, and its predicate does not dereference binary bytes — disk spooling of large bodies stays enabled, so it will not OOM on large multimodal bodies nor be silently skipped.
For the full condition-path syntax (including the Anthropic client's easily-missed tool_result nested-image deep path), JSON shape, and verification steps, see Route image-bearing requests to a vision model — that is the single authoritative page for switch-route. Only fall back to Example 1's script approach when the decision logic goes beyond what structural checks can express.
Example 2: set parameters by access key group
Set different request parameters based on the access key group (context.accessKeyGroup):
function transform(body, context) {
if (context.accessKeyGroup === "premium") {
body.max_tokens = 8192;
} else {
body.max_tokens = 4096;
}
return body;
}Mount in the after slot (request_transform_after) to differentiate quota by group.
Example 3: inject a System Prompt
function transform(body, context) {
if (body.messages.length === 0 || body.messages[0].role !== "system") {
const systemMsg = { role: "system", content: "You are a helpful assistant." };
body.messages = [systemMsg, ...body.messages];
}
return body;
}Mount in the after slot. The simple "inject if missing" logic can also use the append-if-missing rule of request_payload (better performance); scripts are suitable for scenarios that need complex decisions.
FAQ
Q: After a script switches routing, does the original route's after slot still run? No. Once the before slot triggers an immediate useModel routing switch, the whole pipeline is replaced with the target route's, and the original route's after slot does not run. So the request_payload rules must be configured separately on both the original route and the target route.
Q: Can context.useModel switch to a target model with a different protocol from the current model? Yes. The before slot supports cross-protocol switching. But the after slot's useModel can only switch same-protocol — the after slot receives the upstream-format body, and cross-protocol would error.
Q: The script runs too slowly? The main cost of script execution comes from JSON serialization/deserialization. The bigger the body, the slower. For simple field operations prefer request_payload (no serialization cost), and avoid unnecessary deep_clone / json_encode + json_parse.
Q: The script reports TooManyOperations? It exceeded the operation limit (default 2000). Optimize loops, changing O(n²) to O(n). SCRIPT_MAX_OPERATIONS can be adjusted (but optimize the script first).
Next: Scripting API reference for the full built-in function tables; Scripting config for runtime limits and configuration; Switch model by Claude Code plan mode with a script for routing by session marker.
