Script runtime limits and configuration
This chapter covers the script engine's environment variables, limits and safety, and the script testing entry. For the API reference see Scripting API reference; for an intro see Write your first script transform.
Script-related environment variables
| Variable | Default | Purpose |
|---|---|---|
SCRIPT_MAX_OPERATIONS | 2000 | Maximum operations per script execution |
SCRIPT_MEMORY_LIMIT_MB | 64 | Interpreter heap cap (MB) |
SCRIPT_LAZY_BODY | true | Whether to lazily project the request/response body; false takes the legacy whole-materialization branch |
These three control the operation cap and memory; other resource limits (string size, array elements, stack size, etc.) are hardcoded by the sandbox and not configurable. Environment-variable changes require a container restart to take effect. The full env-var list is in Environment variable reference.
Resource limits
| Limit | Value | Configurable |
|---|---|---|
| Max operations | 2,000 | SCRIPT_MAX_OPERATIONS |
| Interpreter heap cap | 64 MB | SCRIPT_MEMORY_LIMIT_MB (a runtime-creation-time constraint; changes require restart) |
| Concurrent execution slots | 4 | server.script_pool_size (a TOML field, not an env var; the number of scripts running concurrently, each slot's heap bounded by SCRIPT_MEMORY_LIMIT_MB) |
| body lazy projection | on | SCRIPT_LAZY_BODY (default true; false takes the legacy whole-materialization branch; rollback is by explicitly switching back; changes require restart) |
| Max string | 10 MB | fixed |
| Max array/object elements | 10,000 | fixed |
| Regex cache | 256 entries, 5-minute TTL | fixed |
| Stack size | 1024 frames | fixed |
ℹ️ body view semantics: with
SCRIPT_LAZY_BODY=true,bodyis a lazy-projection view that materializes on access; the whole body doesn't enter the JS heap, and JS heap usage only tracks the fields the script accesses. The view is consistent with a real object under standard checks likeArray.isArray/instanceof/typeof/Object.keys/ destructuring / spread /JSON.stringify/ array methods. Known boundary:body.x === body.xis false (the shell isn't cached); scripts relying on same-path reference equality must compare by value instead.
Sandbox limits
What scripts can do
- Read and modify
body - Read
contextmetadata - Use all built-in functions (json_path, str_, regex_, array_*, etc.)
- Define helper functions
- Use standard JavaScript (loops, conditionals, closures, destructuring, etc.)
What scripts cannot do
| Forbidden | Reason |
|---|---|
console.log / print | Prevent information leakage |
| File I/O | No filesystem access |
Network / fetch | No network access |
import / require | Cannot load external code |
eval / Function | Cannot dynamically execute code |
globalThis mutation | The global object is frozen |
Error handling
script_error_mode
| Mode | Behavior | Use case |
|---|---|---|
log-and-continue (default) | On script error, logs a warn and continues the request with the original body | Script failure shouldn't block requests |
log-and-reject | On script error, rejects the request and returns a gateway error | Script correctness is critical (e.g. security redaction) |
Common errors
| Error | Cause | Fix |
|---|---|---|
transform() must return a value | Some return path forgot to return body | Every return must return body |
TooManyOperations | Exceeded the operation cap (default 2000) | Optimize loops |
TypeError | Accessing a property of null/undefined | Check with json_path() or the in operator |
ReferenceError | Using an undefined variable or function | Check spelling |
Early returns must return body
function transform(body, context) {
// ✅ correct
if (!body.messages) return body;
// ❌ wrong: returning no value → the script silently fails
if (!body.messages) return;
body.messages[0].content = "modified";
return body;
}Script testing
Test scripts before deploying. The test endpoint POST /console/api/scripts/test needs console auth — pass it via the $CONSOLE_TOKEN environment variable (it is not the upstream access key; it's the console API credential). Get it either of two ways:
Getting CONSOLE_TOKEN
Way 1: configure CONSOLE_SECRET_KEY (recommended for scripts / CI) — set the CONSOLE_SECRET_KEY environment variable; the console uses it as a Bearer token, long-lived and never expires:
# inject at gateway startup
docker run -d -e CONSOLE_SECRET_KEY="my-secret-key-xxx" ... <image>
# pass it when testing scripts
export CONSOLE_TOKEN="my-secret-key-xxx"Way 2: log in with a console account to get a session token — use this path when CONSOLE_SECRET_KEY isn't configured. POST /console/api/login logs in with account/password; the returned token field is the session token (valid 24 hours by default; calls slide-renew it):
export CONSOLE_TOKEN=$(curl -s http://localhost:7890/console/api/login \
-H "Content-Type: application/json" \
-d '{"username":"protoflux","password":"your-password"}' | jq -r .token)CONSOLE_SECRET_KEY and the login password (CONSOLE_PASSWORD) are independent; you can configure only one. See Console login and roles.
curl -X POST http://localhost:7890/console/api/scripts/test \
-H "Authorization: Bearer $CONSOLE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"script": "function transform(body, context) { body.model = context.upstreamModel; return body; }",
"body": {"model": "gpt-4o", "messages": []},
"context": {
"clientModel": "gpt-4o",
"upstreamModel": "gpt-4o-2024-05-13",
"upstreamName": "openai",
"sourceProtocol": "OpenAIChatCompletions",
"targetProtocol": "OpenAIChatCompletions",
"stream": false
}
}'Response:
{
"result_body": {"model": "gpt-4o-2024-05-13", "messages": []},
"execution_time_ms": 0.12
}Performance advice
Performance and memory
Script overhead mainly comes from JSON serialization; latency grows linearly with the body. For the after-slot large-body OOM anti-pattern, latency references, and writing advice, see Script performance and memory.
FAQ
Q: Where do I configure scripts in the console UI? Both the upstream detail page and the model detail page have "Request script" and "Response script" input boxes. You can also set them directly via the Console API.
Q: The script changed body.model, but the route didn't change.body.model only changes the request body's field, not the route. To switch routes use context.useModel("model-name").
Q: Does a response script work for streaming requests? Streaming-response scripts are not supported yet. Request scripts work for streaming requests, but response scripts only take effect for non-streaming.
Q: The script errored but the request passed normally? In the default log-and-continue mode, script errors don't block requests. Check the warn log for the error reason. If the script must succeed, switch to log-and-reject.
Q: Why is there no Authorization in context.headers? Security design: sensitive headers like authorization, cookie, x-api-key are removed before scripts can see them, to prevent credential leakage.
Q: How do I raise the operation cap?SCRIPT_MAX_OPERATIONS is tunable. But optimize the script first — TooManyOperations usually means loop complexity is too high.
Next: Scripting API reference for the full built-in function table; Write your first script transform for the intro structure; Environment variable reference for all environment variables; Audit and security configuration for other security settings.
