feat(plugins): message-middleware plugin SDK for widget and worker#134
Merged
PAMulligan merged 1 commit intoJun 19, 2026
Conversation
Add a ClaudiusPlugin middleware API so consumers can run logic around
chat messages without forking — inject context, redact PII, route to
different models, log analytics, or answer canned intents. Client and
server expose equivalent lifecycle hooks (onBeforeSend / onAfterReceive
/ onError) that may be async and may modify, replace, or short-circuit
messages.
Client (widget):
- ClaudiusPlugin interface + contexts (respondWith / abort short-circuit
on send, respondWith recovery on error) and a runner with per-hook
error isolation.
- ChatWidget accepts a `plugins` prop; hooks run in array order in
useChat — onBeforeSend before the send (transform, canned short-circuit,
or abort), onAfterReceive on the reply, onError recovery.
- Reference plugins pluginAnalytics, pluginRedactPII,
pluginCannedResponses, exported from claudius-chat-widget.
Server (worker):
- ClaudiusServerPlugin with equivalent hooks over {role, content}, a
runner, and a `chatPlugins` Hono middleware (short-circuit, request
transform via c.get("chatRequest"), reply rewrite on afterReceive).
- The same three reference plugins, adapted. Wired into index.ts as an
opt-in extension point — empty by default, so behavior is unchanged.
Docs: rewrite the plugins page with copy-paste client + server examples
and add the `plugins` prop to the widget options table.
Also fixes a latent noImplicitAny error in the worker CORS origin
callback, surfaced now that the new code is type-checked (worker CI
runs tests only, which strip types).
Tested: runner, reference plugins, and useChat integration (client);
runner, reference, isolated middleware, and a real-app success path
(server). Full suites green — widget 287 unit + 10 e2e, worker 39 —
plus widget lint/format/typecheck/docs:api/build/check:exports.
Closes #45
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #45.
Introduces a
ClaudiusPluginmiddleware API so consumers can run logic around chat messages without forking — inject context, redact PII, route to different models, log analytics, or answer canned intents. Both the client (widget) and server (worker) expose equivalent lifecycle hooks.Lifecycle hooks
onBeforeSend(message, ctx)ctx.respondWith), or cancel (ctx.abort)onAfterReceive(message, ctx)onError(err, ctx)Hooks may be async. A hook that throws is caught and logged — one bad plugin can't break the chat.
Client (widget)
ChatWidgetaccepts aplugins: ClaudiusPlugin[]prop; hooks run in array order, wired throughuseChat.ctx.respondWith(reply)short-circuits the network (canned responses);ctx.abort()drops the message;onErrorcanrespondWitha fallback instead of the error UI.pluginAnalytics,pluginRedactPII,pluginCannedResponses.Server (worker)
The Worker exposes the equivalent hooks as Hono middleware over
{ role, content }messages:chatPluginsshort-circuits viarespondWith, hands the transformed request to the route viac.get("chatRequest"), and rewrites the reply ononAfterReceive. It's wired intoworker/src/index.tsas an opt-in extension point — empty by default, so existing behavior is unchanged. The same three reference plugins ship server-side.Design notes
id/sources; the worker uses{ role, content }. The two interfaces mirror each other (matching the issue's "equivalent hooks" wording) without a fragile cross-package shared module.respondWith/abort), keeping hook return types simple (return a transformed message, or nothing).Incidental fix
Type-checking the new worker code surfaced a latent
noImplicitAnyerror in the CORSorigincallback (worker/src/index.ts) — the worker CI runs onlyvitest(esbuild strips types), sotscwas never run there. Fixed with a one-line annotation. Happy to split this out if preferred.Docs
docs/pluginswith copy-paste client + server examples (the page previously listed this as "planned").pluginsprop to the widget options table.1.x/archived docs snapshot is left to thestarlight-versionstooling (not hand-edited). Docs build is validated by thedocs.ymlCI gate.Verification
lint(0 errors),format:check,typecheck,docs:api:check,build,check:exports(attw + publint all green) — 287 unit tests + 10 e2e pass.tsc --noEmitclean — 39 tests pass (runner, reference plugins, isolatedchatPluginsmiddleware, and a real-app success path covering the stashed-request read).🤖 Generated with Claude Code