Skip to content
Lenso
English
Esc
navigateopen⌘Jpreview
On this page

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.

Last updated on September 6, 2026

Was this page helpful?