|
1 | 1 | --- |
2 | 2 | title: Plugins & Tools |
3 | | -description: Extension points available today, and the planned plugin SDK and tool registry. |
| 3 | +description: The Claudius plugin SDK — client and server middleware for message transforms, PII redaction, canned responses, and analytics. |
4 | 4 | --- |
5 | 5 |
|
6 | | -## Extension points today |
| 6 | +Claudius plugins are small middleware that run around the chat message |
| 7 | +lifecycle. The same three lifecycle hooks exist on the **client** (the React |
| 8 | +widget) and the **server** (the Worker), so you can inject context, redact PII, |
| 9 | +route to different models, log analytics events, or answer locally — without |
| 10 | +forking the widget. |
7 | 11 |
|
8 | | -Claudius doesn't have a plugin API yet, but several supported extension points |
9 | | -cover most customization needs: |
| 12 | +## Lifecycle hooks |
| 13 | + |
| 14 | +| Hook | Runs | Can | |
| 15 | +|------|------|-----| |
| 16 | +| `onBeforeSend` | before a message is sent | modify it, replace it, answer locally (`respondWith`), or cancel it (`abort`) | |
| 17 | +| `onAfterReceive` | after the reply arrives | modify or replace the reply | |
| 18 | +| `onError` | when a send fails | observe the error; on the client, recover with a fallback reply | |
| 19 | + |
| 20 | +All hooks may be `async`. A hook that throws is caught and logged, so a single |
| 21 | +misbehaving plugin will not break the chat — write security-sensitive |
| 22 | +transforms (like redaction) defensively. |
| 23 | + |
| 24 | +## Client plugins (widget) |
| 25 | + |
| 26 | +Pass an array of plugins to `ChatWidget`. Hooks run in array order. |
| 27 | + |
| 28 | +```tsx |
| 29 | +import { ChatWidget, pluginRedactPII, pluginAnalytics } from "claudius-chat-widget"; |
| 30 | + |
| 31 | +export function App() { |
| 32 | + return ( |
| 33 | + <ChatWidget |
| 34 | + apiUrl="https://your-worker.workers.dev" |
| 35 | + plugins={[ |
| 36 | + pluginRedactPII(), |
| 37 | + pluginAnalytics({ onEvent: (e) => console.log(e) }), |
| 38 | + ]} |
| 39 | + /> |
| 40 | + ); |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +`onBeforeSend` returns the (possibly modified) message — and the returned |
| 45 | +message is what is **both displayed and sent**, so a redaction is visible to the |
| 46 | +user. |
| 47 | + |
| 48 | +### Short-circuiting |
| 49 | + |
| 50 | +The `onBeforeSend` context can answer without a network call, or cancel the |
| 51 | +send entirely: |
| 52 | + |
| 53 | +```ts |
| 54 | +import type { ClaudiusPlugin } from "claudius-chat-widget"; |
| 55 | + |
| 56 | +const slashCommands: ClaudiusPlugin = { |
| 57 | + name: "slash-commands", |
| 58 | + onBeforeSend(message, ctx) { |
| 59 | + if (message.content === "/clear") return ctx.abort(); |
| 60 | + if (message.content.startsWith("/help")) { |
| 61 | + ctx.respondWith("Type a question and press Enter."); |
| 62 | + } |
| 63 | + }, |
| 64 | +}; |
| 65 | +``` |
| 66 | + |
| 67 | +- `ctx.respondWith(reply)` — render `reply` as the assistant message and skip |
| 68 | + the API. `reply` is a string or `{ content, sources }`. |
| 69 | +- `ctx.abort()` — drop the message and render nothing. |
| 70 | + |
| 71 | +`onError` can recover the same way, replacing the error UI with a reply: |
| 72 | + |
| 73 | +```ts |
| 74 | +const fallback: ClaudiusPlugin = { |
| 75 | + name: "fallback", |
| 76 | + onError: (_err, ctx) => |
| 77 | + ctx.respondWith("We're offline right now — email help@example.com."), |
| 78 | +}; |
| 79 | +``` |
| 80 | + |
| 81 | +### Reference plugins |
| 82 | + |
| 83 | +Three plugins ship with `claudius-chat-widget`. |
| 84 | + |
| 85 | +**`pluginAnalytics`** — emit a structured event for every sent message, reply, |
| 86 | +and error: |
| 87 | + |
| 88 | +```ts |
| 89 | +import { pluginAnalytics } from "claudius-chat-widget"; |
| 90 | + |
| 91 | +pluginAnalytics({ |
| 92 | + onEvent: (event) => window.gtag?.("event", event.type, event), |
| 93 | + includeContent: false, // record only character counts, not message text |
| 94 | +}); |
| 95 | +``` |
| 96 | + |
| 97 | +**`pluginRedactPII`** — strip emails, phone numbers, SSNs, and card-like |
| 98 | +numbers before the message leaves the browser: |
| 99 | + |
| 100 | +```ts |
| 101 | +import { pluginRedactPII } from "claudius-chat-widget"; |
| 102 | + |
| 103 | +pluginRedactPII(); // sensible defaults |
| 104 | +pluginRedactPII({ replacement: "***", redactReplies: true }); |
| 105 | +``` |
| 106 | + |
| 107 | +**`pluginCannedResponses`** — answer matched intents locally, with no API call: |
| 108 | + |
| 109 | +```ts |
| 110 | +import { pluginCannedResponses } from "claudius-chat-widget"; |
| 111 | + |
| 112 | +pluginCannedResponses({ |
| 113 | + rules: [ |
| 114 | + { match: "hours", reply: "We're open 9–5, Mon–Fri." }, |
| 115 | + { match: /pricing|cost/i, reply: "See https://example.com/pricing." }, |
| 116 | + ], |
| 117 | +}); |
| 118 | +``` |
| 119 | + |
| 120 | +### Writing your own |
| 121 | + |
| 122 | +A plugin is an object with a `name` and any of the three hooks: |
| 123 | + |
| 124 | +```ts |
| 125 | +import type { ClaudiusPlugin } from "claudius-chat-widget"; |
| 126 | + |
| 127 | +const pageContext: ClaudiusPlugin = { |
| 128 | + name: "page-context", |
| 129 | + onBeforeSend(message) { |
| 130 | + return { ...message, content: `[on ${location.pathname}] ${message.content}` }; |
| 131 | + }, |
| 132 | +}; |
| 133 | +``` |
| 134 | + |
| 135 | +## Server plugins (Worker) |
| 136 | + |
| 137 | +The Worker exposes the equivalent hooks as Hono middleware. Server hooks |
| 138 | +operate on the whole request (the messages array) and the reply string, rather |
| 139 | +than on a single widget message. |
| 140 | + |
| 141 | +Register plugins in `worker/src/index.ts` — the file ships this block with an |
| 142 | +empty list, so just drop your plugins in: |
| 143 | + |
| 144 | +```ts |
| 145 | +import { chatPlugins, pluginRedactPII } from "./plugins"; |
| 146 | + |
| 147 | +const serverPlugins = [pluginRedactPII({ redactReplies: true })]; |
| 148 | +if (serverPlugins.length > 0) { |
| 149 | + app.use("/api/chat", chatPlugins(serverPlugins)); |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +### Server hooks |
| 154 | + |
| 155 | +```ts |
| 156 | +import type { ClaudiusServerPlugin } from "./plugins"; |
| 157 | + |
| 158 | +const example: ClaudiusServerPlugin = { |
| 159 | + name: "example", |
| 160 | + onBeforeSend(messages, ctx) { |
| 161 | + // Inspect ctx.env, transform messages, or short-circuit the model: |
| 162 | + // ctx.respondWith("a canned reply"); |
| 163 | + return messages; |
| 164 | + }, |
| 165 | + onAfterReceive(reply) { |
| 166 | + return reply.trim(); |
| 167 | + }, |
| 168 | + onError(error) { |
| 169 | + console.error("chat failed:", error.message); |
| 170 | + }, |
| 171 | +}; |
| 172 | +``` |
| 173 | + |
| 174 | +- `onBeforeSend(messages, ctx)` — return a new messages array, or call |
| 175 | + `ctx.respondWith(text)` to answer without calling Claude. |
| 176 | +- `onAfterReceive(reply, ctx)` — return a new reply string. |
| 177 | +- `onError(error, ctx)` — observe failures. |
| 178 | + |
| 179 | +### Server reference plugins |
| 180 | + |
| 181 | +The same three plugins, adapted for the request lifecycle: |
| 182 | + |
| 183 | +```ts |
| 184 | +import { |
| 185 | + chatPlugins, |
| 186 | + pluginAnalytics, |
| 187 | + pluginRedactPII, |
| 188 | + pluginCannedResponses, |
| 189 | +} from "./plugins"; |
| 190 | + |
| 191 | +const serverPlugins = [ |
| 192 | + pluginRedactPII(), // redact every message before it reaches Claude |
| 193 | + pluginCannedResponses({ |
| 194 | + rules: [{ match: /refund/i, reply: "See our refund policy at /refunds." }], |
| 195 | + }), |
| 196 | + pluginAnalytics({ onEvent: (e) => console.log(e) }), |
| 197 | +]; |
| 198 | + |
| 199 | +app.use("/api/chat", chatPlugins(serverPlugins)); |
| 200 | +``` |
| 201 | + |
| 202 | +Redacting server-side is defense in depth — it runs even for clients that don't |
| 203 | +ship the widget redactor. A canned (short-circuit) response skips the model, and |
| 204 | +with it the built-in D1 analytics for that turn. |
| 205 | + |
| 206 | +## Notes |
| 207 | + |
| 208 | +- Hooks run in array order; the first `respondWith` / `abort` wins and stops |
| 209 | + that hook's chain. |
| 210 | +- A throwing hook is caught and logged — it never breaks the chat. |
| 211 | +- The client and server interfaces are parallel but not identical: a widget |
| 212 | + message carries an `id` and `sources`; the server works on plain |
| 213 | + `{ role, content }` messages. |
| 214 | + |
| 215 | +## Other extension points |
| 216 | + |
| 217 | +These complement plugins and cover most customization without code: |
10 | 218 |
|
11 | 219 | | Extension point | What it controls | |
12 | 220 | |-----------------|------------------| |
13 | 221 | | [System prompt](/configuration/worker/#system-prompt) (`worker/src/system-prompt.ts`) | The bot's personality, knowledge, guardrails, and FAQ answers | |
14 | 222 | | [Translations](/configuration/localization/) | Every user-facing UI string | |
15 | | -| [Theming options](/configuration/theming/) and `widget/tailwind.config.ts` | Colors, fonts, radii | |
| 223 | +| [Theming](/configuration/theming/) and `widget/tailwind.config.ts` | Colors, fonts, radii | |
16 | 224 | | [Proactive triggers](/configuration/triggers/) | When the widget opens or greets proactively | |
17 | 225 | | Worker env vars (`CLAUDE_MODEL`, `MAX_TOKENS`, rate limits) | Model behavior and abuse protection | |
18 | | -| Fork-level: `widget/src/` and `worker/src/` | Anything else — both packages are small, typed, and well-tested | |
19 | | - |
20 | | -For testing integrations, the widget exports a `MockChatApiClient` test |
21 | | -utility (`widget/src/test-utils/`) that fakes the API client without network |
22 | | -calls. |
23 | 226 |
|
24 | 227 | ## Planned |
25 | 228 |
|
26 | | -These are tracked on the [v1.3.0 and v2.0.0 milestones](https://github.com/PMDevSolutions/Claudius/milestones): |
27 | | - |
28 | 229 | - **Anthropic tool-use / function calling** with a declarative tool registry, |
29 | 230 | so the bot can call your APIs mid-conversation — |
30 | 231 | [#51](https://github.com/PMDevSolutions/Claudius/issues/51) |
31 | | -- **Plugin/hook SDK** for message middleware: pre-send and post-receive |
32 | | - transforms — [#45](https://github.com/PMDevSolutions/Claudius/issues/45) |
33 | 232 | - **Plugin SDK RFC for Claudius v2** — the longer-term plugin architecture — |
34 | 233 | [#79](https://github.com/PMDevSolutions/Claudius/issues/79) |
35 | | - |
36 | | -Nothing on this list is shipped yet. If you need one of these, comment on the |
37 | | -issue — it helps prioritization. |
|
0 commit comments