#Making a plugin, is it possible to rewrite user's messages before sending it to AI providers?
1 messages · Page 1 of 1 (latest)
Short answer: partially, but not quite in the Presidio-shaped way yet.
I checked current docs/source. The plugin SDK does have api.registerTextTransforms({ input, output }): input rewrites the final system prompt + text message content before provider/CLI transport, and output rewrites assistant text before OpenClaw consumes/delivers it.
But that surface is just deterministic string | RegExp -> string replacements. It is not an async “call Presidio, rewrite this turn, continue with the normal model path” hook.
The nearby hooks don’t quite solve it either:
llm_inputis observe-only.message_receivedis fire-and-forget observe-only.before_dispatchcan intercept/handle a message, but not return “continue with this rewritten user message”.before_message_writecan mutate/block persisted session messages, but it’s sync-only — awkward for Presidio unless you already have the mapping locally.
So for “provider never sees PII”, static transforms can work for simple cases. For “session stays PII-free using Presidio”, I’d call that a missing first-class SDK seam: probably an async pre-dispatch/pre-provider sanitization hook that can mutate the user message before persistence + provider transport, with optional output de-anonymization if desired.
Docs I’d start from: https://docs.openclaw.ai/plugins/building-plugins and https://docs.openclaw.ai/plugins/sdk-provider-plugins
This is a good feature-request shape for GitHub: https://github.com/openclaw/openclaw/issues 🦞
Is the alternative be creating a custom provider plugin + custom ai proxy that filters out things before sending to upstream...? I mean I can do that, and if I can also add in image generation to be 1:1 with openai/openai-codex, that'll be great too.
LiteLLM is too heavy for me. What do you think Krill?
Yeah — if you want this today, I’d do the custom provider/proxy path. It’s not the prettiest seam, but it’s the cleanest one you control.
My take:
-
Lightweight proxy is reasonable
Make an OpenAI/Codex-ish proxy that accepts OpenClaw’s provider request, runs Presidio/anonymization, forwards upstream, then optionally de-anonymizes the assistant response. That’s way lighter than LiteLLM if you only need one or two upstreams. -
Wrap it as an OpenClaw provider plugin
Register a new provider id, e.g.pii-openai/redacted-openai, and have the plugin point at your proxy. That keeps OpenClaw config clean and avoids pretending you are the built-in provider. -
But be careful about “session stays PII-free”
A provider/proxy can ensure the upstream AI provider doesn’t see raw PII.
It does not automatically mean OpenClaw’s local session JSONL/history is PII-free, because the raw inbound message may already be persisted/used before the provider request depending on path.For true “local session is PII-free too”, you also want a persistence-side strategy: probably
before_message_writeto rewrite/block stored messages. Current catch: that hook is sync-only, so Presidio-over-HTTP is awkward unless you do the Presidio work earlier and keep a local mapping/cache. -
Image generation can be part of the same plugin
The SDK does expose registerImageGenerationProvider(...), and the built-in OpenAI plugin uses that route. So your plugin can register both:
- text/model provider
- image generation provider
That gets you much closer to “one redacted provider namespace that covers chat + image gen”.
The architecture I’d choose:
- OpenClaw plugin registers
redacted-openai/* - Plugin talks only to your tiny local proxy
- Proxy does Presidio anonymize → upstream call → optional deanonymize
- Plugin also registers image generation provider and routes image prompts/reference metadata through the same redaction policy
- Add a
before_message_writehook only if you need local OpenClaw sessions to be scrubbed too
The one thing I would not do is try to clone all of LiteLLM. Keep it boring: one proxy, one upstream family, explicit supported endpoints. Boring is good here. 🦞
Relevant docs:
I just want upstream to not see PII, and local can store PII whatever it wants to.