π Plugin System
Tresor's JavaScript plugin system lets you build custom traffic modifications without rebuilding or restarting the gateway. Transform requests, responses, and streaming events, then compose your transformations into ordered routing-rule pipelines. Tresor embeds the runtime, so no Node.js installation is required.
β¨ What can you build?β
- Modify request headers, paths, queries, and bodiesβfor example, inject instructions or reshape messages and tool data.
- Rewrite buffered response bodies and headers, normalize provider-specific payloads, or filter unwanted content.
- Transform SSE events with per-request state, request upstream streaming, and collect streams into buffered responses.
- Combine multiple plugins and target them by request path, model, client format, downstream format, or downstream ID.
The hooks form a general-purpose traffic transformation layer; the examples below are starting points, not a list of everything the system can do. See the lifecycle and runtime boundaries below for the supported effects and execution model.
π Hot reloadβ
Install and enable packages while the daemon is runningβno gateway restart, binary rebuild, or client reconfiguration is needed. Installing a new version enables it for future request snapshots. Requests already in flight keep their pinned package, so an update does not replace code partway through a response.
To iterate on your own plugin, edit its source, bump the manifest version, and install the directory again. You can also enable or disable installed versions live. Hot reload is package-based: editing a source file alone does not update the installed package.
API format conversion is built into Tresor and selected automatically; plugins let you customize traffic beyond that baseline.
π οΈ Built-in format conversionβ
Tresor automatically selects converters when the client format differs from the downstream's declared api_formats. These internal converters handle requests, responses, and SSE streams; they are not installable packages and are not offered in the rule plugin picker.
| Client format | Native converters |
|---|---|
| OpenAI Chat Completions | openai2anthropic, openai2responses, openai2gemini |
| Anthropic Messages | anthropic2openai, anthropic2responses, anthropic2gemini |
| OpenAI Responses | responses2openai, responses2anthropic |
| Google Gemini | gemini2openai, gemini2anthropic, gemini2responses |
No routing rule is needed for format conversion. Configure downstream formats and model routing normally; do not add converter IDs to user pipelines.
π¦ Example plugins and inspirationβ
Explore Ladbaby/Tresor-plugins for complete, installable examples. Read their source for inspiration, adapt an approach to your own needs, or build a completely different transformation:
| ID | Purpose |
|---|---|
break_repeat | Inserts a reminder after repeated comparable assistant turns |
fix_anthropic_system | Moves system-role messages into Anthropic's top-level system field |
fix_anthropic_usage | Normalizes incomplete Anthropic usage reporting |
force_streaming | Requests upstream SSE for buffered clients and reconstructs a JSON response |
remove_thinking | Removes thinking/reasoning response content while preserving visible content and tool inputs |
Use the repository's README and each package's manifest for current versions and feature requirements. Plugins are optional and apply only when an enabled routing rule includes their ID.
β‘ Install and enableβ
Start the Tresor daemon first. Choose a versioned JSON bundle from Tresor-plugins Releases, not a repository ZIP or an individual JavaScript file.
# Example published bundle; add --config if your daemon uses a custom config path.
tresor plugin install-url "https://github.com/Ladbaby/Tresor-plugins/releases/download/v1.0.2/remove_thinking-2.0.2.json"
# Alternatively, install a local source directory containing manifest.json.
tresor plugin install ./remove_thinking
tresor plugin list
In the dashboard, open Rules > Plugins to install the bundle's HTTPS URL or manage installed versions. Installation enables the installed version immediately; no separate approval or activation step is needed. Only one version per plugin ID is enabled at a time.
Install only trusted code. To check a known release, add --sha256 <canonical-source-digest> using the value in its plugin-digests.txt. This is Tresor's canonical source digest, not a checksum of the downloaded JSON file. Obtain the digest from a trusted source. HTTPS alone is not publisher verification. Signed bundles can additionally be checked with --signature <base64> and --public-key <base64> using a trusted Ed25519 publisher key.
tresor plugin disable remove_thinking 2.0.2
tresor plugin enable remove_thinking 2.0.2
tresor plugin delete remove_thinking 2.0.2
plugin activate <id> <version> remains a compatibility alias for enabling. Changes affect future request snapshots; in-flight requests retain their pinned package.
π Add a plugin to a ruleβ
Enabling a package makes it available, but does not apply it globally. Add it to a routing rule in the Web UI or YAML:
rules:
- id: hide-reasoning
name: Hide reasoning
pattern_path: '*'
pipeline_config:
- plugin_id: remove_thinking
is_enabled: true
In the admin API and database, pipeline_config is an ordered JSON array:
[
{"plugin_id": "fix_anthropic_usage"},
{"plugin_id": "remove_thinking"}
]
Each step contains only plugin_id. There are no per-plugin user settings or configurable failure policies. Hook failures raise errors rather than silently bypassing the plugin. Order matters: each transformation feeds the next. Multiple matching rules contribute pipelines in rule priority order; rules never choose the downstream.
βοΈ Write a JavaScript API V2 packageβ
A plugin is a directory containing manifest.json, a CommonJS entry file, and any package-relative modules. Node.js is not required to run it: Tresor embeds a JavaScript runtime.
Example manifest.json:
{
"id": "example/request-header",
"version": "1.0.0",
"api_version": 2,
"entry": "plugin.js",
"requires": [],
"permissions": [],
"config_schema": {}
}
Example plugin.js (a fixed transformation, not user-configurable):
module.exports = {
request({ request, body, context, state }) {
return {
request: {
setHeaders: { "x-example": ["enabled"] }
}
};
}
};
Inputs are immutable snapshots; return an effect rather than modifying them. Returning undefined leaves the relevant input unchanged. state is mutable per-plugin request state. Payloads use { encoding: "utf8" | "base64", data: string }; headers use arrays of strings. Hook execution is synchronous: Promises are unsupported.
The authoritative contract is internal/jsplugins/api.d.ts. Check it against the Tresor version you target, and use the published packages as complete examples.
π Lifecycle and transportβ
| Hooks | Purpose |
|---|---|
request | Transform outgoing request metadata/body and optionally request transport behavior |
responseHeaders, response | Transform headers and buffered response bodies |
upstreamStart, upstreamEvent, upstreamEnd | Process upstream SSE before collection or regular response-event transforms |
streamStart, streamEvent, streamEnd | Transform regular response SSE events |
collectStart, collectEvent, collectEnd | Collect upstream SSE into a buffered response |
finish | Cleanup on completion or error |
The immutable context distinguishes client, downstream, and current formats; requested versus observed HTTP/SSE transport; stage; phase; and effective limits. Never infer all of these from the original client request.
Declare required host features in the manifest. Upstream hooks require sse.upstream.v2; collection requires sse.collect.v2 and all three collection hooks. Ordinary stream hooks do not automatically run before collection. Error termination provides sanitized failure metadata and statistics, and finish releases managed storage. See api.d.ts for exact hook inputs, effects, and supported feature names.
π¦ Lint, pack, and publishβ
# Static checks only; neither command executes JavaScript.
tresor plugin lint ./my-plugin
tresor plugin lint ./my-plugin --json --strict
# Output must be a new file. Pack prints the canonical source digest.
tresor plugin pack ./my-plugin --output ./my-plugin-1.0.0.json
# Install into the running daemon for integration testing.
tresor plugin install ./my-plugin
Lint findings are advisory unless --strict is used. Lint is not a compatibility proof: test real gateway requests, including malformed payloads, SSE termination, collection, and cleanup. Bump the manifest version whenever published source changes. Publish versioned JSON bundles and canonical digests rather than repository ZIPs.
π Runtime boundariesβ
Plugins are trusted code, not a hard security sandbox. The runtime exposes neither Node/browser APIs nor arbitrary filesystem/network access. Imports are limited to package-relative JavaScript modules and provided tresor/* libraries. Non-empty permissions, user configuration, and logging capabilities are unsupported; plugin logs and execution diagnostics are not collected or published.
Tresor validates effects and enforces host-selected body, event, state, buffer, and wire limits, but these are not a hard memory sandbox. There are no execution timers or watchdogs for initialization or hooks. Authors must ensure their code terminates.
π Admin APIβ
Package management endpoints require admin authentication:
| Method | Path | Purpose |
|---|---|---|
| GET | /api/plugins | Enabled executable external plugins for rule selection; excludes native converters |
| GET | /api/plugin-packages | Installed versions and availability |
| POST | /api/plugin-packages/install | Install local source as {files: {...}} |
| POST | /api/plugin-packages/install-remote | Install HTTPS {url, sha256?, signature?, public_key?} bundle |
| POST | /api/plugin-packages/enabled | Set {id, version, enabled} |
| POST | /api/plugin-packages/delete | Delete {id, version} |
| POST | /api/plugin-packages/activate | Compatibility alias for enabling |