Bun Plugin
Build a typed request Plugin with the supported Bun SDK and CLI path.
Bun is a trusted child-process execution class, not a sandbox. Plugin business
code implements generated Capability Provider types; @lenso/bun and generated
entrypoints own startup, JSON-RPC, cancellation, and shutdown.
1. Create the project
lenso plugin new example.echo --runtime bun
cd example.echo
Creation stages the whole directory, installs the exact bun.lock, and runs the
TypeScript check before publishing the project. Use --no-install only for an
offline scaffold.
example.echo/
├── package.json
├── bun.lock
├── tsconfig.json
└── src/
├── plugin.ts
├── lenso.bun.generated.ts
├── lenso.describe.generated.ts
└── lenso.invoke.generated.ts
Edit only src/plugin.ts. The initial scaffold implements the generated
lenso.agent.tool-provider@2 Provider and exposes one Tool. Expected business
outcomes return generated Domain Errors; thrown exceptions remain Runtime
Failures.
2. Implement the Provider
The authored file exports one Plugin definition. Generated files own the Bun server, descriptor projection, and development invocation:
import { definePlugin } from "@lenso/bun";
import {
bindToolProviderProvider,
type ToolProviderProvider,
} from "@lenso/bun/capabilities/agent-tool-provider";
const tool: ToolProviderProvider = {
async catalog() {
return { ok: true, value: { tools: [{
name: "company.uppercase",
description: "Convert text to uppercase.",
input_schema_json: JSON.stringify({
type: "object",
additionalProperties: false,
properties: { text: { type: "string", maxLength: 4096 } },
required: ["text"],
}),
}] } };
},
async execute(_context, request) {
if (request.name !== "company.uppercase") {
return { ok: false, error: { kind: "domain", error: "not_found" } };
}
let input: unknown;
try {
input = JSON.parse(request.arguments_json);
} catch {
return { ok: false, error: { kind: "domain", error: "invalid_arguments" } };
}
if (typeof input !== "object" || input === null || !("text" in input)
|| typeof input.text !== "string") {
return { ok: false, error: { kind: "domain", error: "invalid_arguments" } };
}
return { ok: true, value: {
content: input.text.toUpperCase(),
content_type: "text",
metadata_json: "{}",
} };
},
};
export default definePlugin({ providers: [bindToolProviderProvider(tool)] });
Keep input validation and expected business rejection inside the generated Domain Error union. Reserve thrown exceptions for broken implementation or runtime conditions.
3. Check and develop
lenso plugin check
lenso plugin dev \
--operation execute \
--request-json '{"name":"example.echo","arguments_json":"{\"text\":\"hello\"}"}'
lenso plugin dev --watch \
--operation execute \
--request-json '{"name":"example.echo","arguments_json":"{\"text\":\"hello\"}"}'
check type-checks, builds the Bun implementation, derives its portable
Descriptor, materializes a standard Bundle, and verifies the closure. dev
invokes the same authored Provider without asking authors to implement a test
wire. Watch mode stays alive after a failed rebuild.
4. Package and install
lenso plugin pack
lenso plugins add dist/example.echo-0.1.0.lenso-plugin --root "$HOME/.lenso/agent"
The archive selects lenso.bun-process@1. A compatible product Host must link
the Bun Adapter and the generated Rust codec for every provided Capability;
the Agent Host includes that integration. The machine running the Host must
have bun available.
Current Bun Authoring V2 supports Request, bidirectional Stream, and Event
Providers, plus generated outbound dependency clients for all three interactions.
@lenso/bun 0.5.1 and @lenso/bun-plugin 0.4.1 are recorded released versions;
the earlier Request-only limit no longer applies to that SDK cohort.
See @lenso/bun for the typed SDK
and lenso-bun-adapter for the
process boundary.
Author stateful Plugins with generated Capabilities
The providers/bind*Provider example above remains a compatibility entrypoint.
For new code, prefer generated Capability values, provides, and create.
This structural example assumes your project generated Conversation and
Store contracts with the corresponding chat/get operations:
import { definePlugin } from "@lenso/bun";
import { Conversation } from "./generated/conversation.ts";
import { Store } from "./generated/store.ts";
export default definePlugin({
provides: [Conversation],
dependencies: { store: Store.required() },
async create({ dependencies }) {
return {
async *chat(_context, request) {
const greeting = await dependencies.store.get(request.room);
yield { text: greeting ?? "Hello" };
},
};
},
});
Dependency keys default to stable requirement IDs. required(), optional(),
and many() work across all three interactions. create constructs each
Instance; managed shutdown calls stop at most once. Lifecycle-only Plugins
with no provided Capability are valid too.
Server-output Streams can return async generators. Use the generated
StreamSession for bidirectional messages, independent half-close, or terminal
domain errors. Event handlers return void or Promise<void>; publication
waits for completion and rejection becomes Runtime Failure. Outbound clients
use exact Plan-selected providers and preserve cancellation, ordering, and
bounded admission semantics.
See the Bun SDK for generated interfaces. Continue with named dependencies, the complete sync example, or TypeScript Host authoring. The Host build profile has separate limits.