Plugins
definePlugin — discriminated union by kind, sealed PluginContext, closed HookName enum.
Canonical contract:
packages/sdk/docs.md— ADRs D97-D104.
Plugins are the extension point of the SDK. The contract lives at internal/plugins/ (ADR D97) and reaches consumers via definePlugin + the Plugin discriminated union.
Plugin kinds
type Plugin =
| { kind: "hook"; ... } // lifecycle hooks
| { kind: "tool"; ... } // toolset provider
| { kind: "cache"; ... } // semantic cache (Cache.semantic().asPlugin())
| { kind: "telemetry"; ... } // custom telemetry sink
| ... // see docs.md for full listThe union is closed by kind (ADR D98) — TypeScript forces exhaustive handling.
definePlugin
import { definePlugin } from "@theokit/sdk";
const myPlugin = definePlugin({
kind: "hook",
name: "rate-limit-tool-calls",
hooks: {
pre_tool_call: async (ctx) => {
if (await isRateLimited(ctx.agentId)) {
return { block: true, message: "Slow down." };
}
},
},
});
const agent = await Agent.create({ apiKey, model, plugins: [myPlugin] });PluginContext (sealed in dev mode)
The context object passed to plugin hooks is sealed via Proxy in dev mode (ADR D99). Trying to assign to it throws — catches accidental mutation early.
In production, the Proxy is bypassed (no overhead).
Hooks
See the Hooks concept — 8 closed hook names (ADR D100), pre_tool_call is the only veto hook (ADR D101).
Toolset plugins
A plugin with kind: "tool" provides a Toolset. The ToolRegistry is 3-layer (ADR D102):
- Registration — plugin declares its tools.
- Exposure — agent decides which subset to surface to the model per turn (via
check_fnpredicate, TTL-cached 30s — ADR D103). - Availability — per-turn check.
A Toolset is a flat list — no extends (ADR D104). Composition via multiple plugins.
Discovery
Beyond programmatic plugins: [...], the SDK loads .theokit/plugins/*.ts (project) and ~/.theokit/plugins/*.ts (user). Each file must export default a Plugin.