426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
956 lines
22 KiB
Plaintext
956 lines
22 KiB
Plaintext
---
|
|
title: "Development"
|
|
description: "Build plugins for elizaOS in TypeScript, Python, or Rust"
|
|
---
|
|
|
|
## Your Plugin in 3 Steps
|
|
|
|
<Tabs>
|
|
<Tab title="TypeScript">
|
|
1. **Scaffold** - `elizaos create my-plugin --template plugin` 2.
|
|
**Build** - Add actions, providers, or services 3. **Test** - `bun run
|
|
test`
|
|
</Tab>
|
|
<Tab title="Python">
|
|
1. **Create** - `mkdir plugin-my-plugin && cd plugin-my-plugin` 2. **Build**
|
|
- Define your plugin with actions and providers 3. **Test** - `pytest`
|
|
</Tab>
|
|
<Tab title="Rust">
|
|
1. **Create** - `cargo new plugin-my-plugin --lib` 2. **Build** - Implement
|
|
the Plugin trait 3. **Test** - `cargo test`
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
That's it. No complex setup, no boilerplate to maintain.
|
|
|
|
<Tip>
|
|
**30 minutes to your first plugin.** Focus on your logic, not infrastructure.
|
|
elizaOS plugins work identically across TypeScript, Python, and Rust.
|
|
</Tip>
|
|
|
|
## Quick Start: Scaffolding Plugins with CLI
|
|
|
|
The easiest way to create a new plugin is using the elizaOS CLI, which provides interactive scaffolding with pre-configured templates.
|
|
|
|
### Using `elizaos create`
|
|
|
|
The CLI offers two plugin templates to get you started quickly:
|
|
|
|
```bash
|
|
# Interactive plugin creation
|
|
elizaos create
|
|
|
|
# Or specify the name directly
|
|
elizaos create my-plugin --template plugin
|
|
```
|
|
|
|
When creating a plugin, you'll be prompted to choose between:
|
|
|
|
1. **Quick Plugin (Backend Only)** - Simple backend-only plugin without frontend
|
|
- Perfect for: API integrations, blockchain actions, data providers
|
|
- Includes: Basic plugin structure, actions, providers, services
|
|
- No frontend components or UI routes
|
|
|
|
2. **Full Plugin (with Frontend)** - Complete plugin with React frontend and API routes
|
|
- Perfect for: Plugins that need web UI, dashboards, or visual components
|
|
- Includes: Everything from Quick Plugin + React frontend, Vite setup, API routes
|
|
- Tailwind CSS pre-configured for styling
|
|
|
|
### Quick Plugin Structure
|
|
|
|
After running `elizaos create` and selecting "Quick Plugin", you'll get:
|
|
|
|
```
|
|
plugin-my-plugin/
|
|
├── src/
|
|
│ ├── index.ts # Plugin manifest
|
|
│ ├── actions/ # Your agent actions
|
|
│ │ └── example.ts
|
|
│ ├── providers/ # Context providers
|
|
│ │ └── example.ts
|
|
│ └── types/ # TypeScript types
|
|
│ └── index.ts
|
|
├── package.json # Pre-configured with elizaos deps
|
|
├── tsconfig.json # TypeScript config
|
|
├── build.ts # Build script using Bun.build
|
|
└── README.md # Plugin documentation
|
|
```
|
|
|
|
### Full Plugin Structure
|
|
|
|
Selecting "Full Plugin" adds frontend capabilities:
|
|
|
|
```text
|
|
plugin-my-plugin/
|
|
├── src/
|
|
│ ├── index.ts # Plugin manifest with routes
|
|
│ ├── actions/
|
|
│ ├── providers/
|
|
│ ├── types/
|
|
│ └── frontend/ # React frontend
|
|
│ ├── App.tsx
|
|
│ ├── main.tsx
|
|
│ └── components/
|
|
├── public/ # Static assets
|
|
├── index.html # Frontend entry
|
|
├── vite.config.ts # Vite configuration
|
|
├── tailwind.config.js # Tailwind setup
|
|
└── [other config files]
|
|
```
|
|
|
|
### After Scaffolding
|
|
|
|
Once your plugin is created:
|
|
|
|
```bash
|
|
# Navigate to your plugin
|
|
cd plugin-my-plugin
|
|
|
|
# Install dependencies (automatically done by CLI)
|
|
bun install
|
|
|
|
# Build your plugin for distribution
|
|
bun run build
|
|
|
|
# Run tests
|
|
bun run test
|
|
```
|
|
|
|
The scaffolded plugin includes:
|
|
|
|
- ✅ Proper TypeScript configuration
|
|
- ✅ Build setup with Bun.build (and Vite for full plugins)
|
|
- ✅ Example action and provider to extend
|
|
- ✅ Integration with `@elizaos/core`
|
|
- ✅ Development scripts ready to use
|
|
- ✅ Basic tests structure
|
|
|
|
<Tip>
|
|
The CLI templates follow all elizaOS conventions and best practices, making it
|
|
easy to get started without worrying about configuration.
|
|
</Tip>
|
|
|
|
## Manual Plugin Creation
|
|
|
|
If you prefer to create a plugin manually or need custom configuration:
|
|
|
|
### 1. Initialize the Project
|
|
|
|
```bash
|
|
mkdir plugin-my-custom
|
|
cd plugin-my-custom
|
|
bun init
|
|
```
|
|
|
|
### 2. Install Dependencies
|
|
|
|
```bash
|
|
# Core dependency
|
|
bun add @elizaos/core
|
|
|
|
# Development dependencies
|
|
bun add -d typescript @types/node
|
|
```
|
|
|
|
### 3. Configure TypeScript
|
|
|
|
Create `tsconfig.json`:
|
|
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"target": "ES2022",
|
|
"module": "ESNext",
|
|
"moduleResolution": "bundler",
|
|
"lib": ["ES2022"],
|
|
"rootDir": "./src",
|
|
"outDir": "./dist",
|
|
"strict": true,
|
|
"esModuleInterop": true,
|
|
"skipLibCheck": true,
|
|
"forceConsistentCasingInFileNames": true,
|
|
"declaration": true,
|
|
"declarationMap": true,
|
|
"sourceMap": true,
|
|
"types": ["node"]
|
|
},
|
|
"include": ["src/**/*"],
|
|
"exclude": ["node_modules", "dist"]
|
|
}
|
|
```
|
|
|
|
### 4. Configure Build
|
|
|
|
Create `build.ts`:
|
|
|
|
```typescript
|
|
#!/usr/bin/env bun
|
|
|
|
/**
|
|
* Build script for your plugin
|
|
* Uses Bun.build for bundling
|
|
*/
|
|
|
|
import { existsSync } from "node:fs";
|
|
import { rm } from "node:fs/promises";
|
|
|
|
const externalDeps = ["@elizaos/core"];
|
|
|
|
async function buildPlugin() {
|
|
console.log("🔨 Building plugin...\n");
|
|
|
|
// Clean dist directory
|
|
if (existsSync("dist")) {
|
|
await rm("dist", { recursive: true, force: true });
|
|
}
|
|
|
|
// Build with Bun
|
|
console.log("📦 Bundling with Bun...");
|
|
const buildResult = await Bun.build({
|
|
entrypoints: ["src/index.ts"],
|
|
outdir: "dist",
|
|
target: "node",
|
|
format: "esm",
|
|
sourcemap: "external",
|
|
minify: false,
|
|
external: externalDeps,
|
|
});
|
|
|
|
if (!buildResult.success) {
|
|
console.error("Build failed:");
|
|
for (const log of buildResult.logs) {
|
|
console.error(log);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`✅ Built ${buildResult.outputs.length} file(s)`);
|
|
|
|
// Generate type declarations with tsc
|
|
console.log("📝 Generating type declarations...");
|
|
const tscProcess = Bun.spawn(["bunx", "tsc", "-p", "tsconfig.build.json"], {
|
|
stdout: "inherit",
|
|
stderr: "inherit",
|
|
});
|
|
await tscProcess.exited;
|
|
|
|
if (tscProcess.exitCode !== 0) {
|
|
console.error("TypeScript declaration generation failed");
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("\n✅ Build complete!");
|
|
}
|
|
|
|
buildPlugin().catch((error) => {
|
|
console.error("Build failed:", error);
|
|
process.exit(1);
|
|
});
|
|
```
|
|
|
|
### 5. Create Plugin Structure
|
|
|
|
<Tabs>
|
|
<Tab title="TypeScript">
|
|
Create `src/index.ts`:
|
|
|
|
```typescript
|
|
import type { Plugin } from "@elizaos/core";
|
|
import { myAction } from "./actions/myAction";
|
|
import { myProvider } from "./providers/myProvider";
|
|
import { MyService } from "./services/myService";
|
|
|
|
export const myPlugin: Plugin = {
|
|
name: "my-custom-plugin",
|
|
description: "A custom plugin for elizaOS",
|
|
|
|
actions: [myAction],
|
|
providers: [myProvider],
|
|
services: [MyService],
|
|
|
|
init: async (config, runtime) => {
|
|
console.log("Plugin initialized");
|
|
},
|
|
};
|
|
|
|
export default myPlugin;
|
|
```
|
|
|
|
</Tab>
|
|
<Tab title="Python">
|
|
Create `plugin.py`:
|
|
|
|
```python
|
|
from elizaos import Plugin, Action, Provider
|
|
|
|
from .actions.my_action import my_action
|
|
from .providers.my_provider import my_provider
|
|
|
|
my_plugin = Plugin(
|
|
name="my-custom-plugin",
|
|
description="A custom plugin for elizaOS",
|
|
actions=[my_action],
|
|
providers=[my_provider],
|
|
)
|
|
|
|
async def init(config, runtime):
|
|
print("Plugin initialized")
|
|
|
|
my_plugin.init = init
|
|
```
|
|
|
|
</Tab>
|
|
<Tab title="Rust">
|
|
Create `src/lib.rs`:
|
|
|
|
```rust
|
|
use elizaos::{Plugin, Action, Provider, IAgentRuntime};
|
|
use anyhow::Result;
|
|
|
|
mod actions;
|
|
mod providers;
|
|
|
|
pub fn create_my_plugin() -> Result<Plugin> {
|
|
Ok(Plugin {
|
|
name: "my-custom-plugin".to_string(),
|
|
description: "A custom plugin for elizaOS".to_string(),
|
|
actions: vec![actions::my_action()],
|
|
providers: vec![providers::my_provider()],
|
|
init: Some(Box::new(|_config, _runtime| {
|
|
Box::pin(async {
|
|
println!("Plugin initialized");
|
|
Ok(())
|
|
})
|
|
})),
|
|
..Default::default()
|
|
})
|
|
}
|
|
```
|
|
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
### 6. Update package.json
|
|
|
|
```json
|
|
{
|
|
"name": "@myorg/plugin-custom",
|
|
"version": "0.1.0",
|
|
"main": "dist/index.js",
|
|
"types": "dist/index.d.ts",
|
|
"type": "module",
|
|
"scripts": {
|
|
"build": "bun run build.ts",
|
|
"dev": "bun --hot build.ts",
|
|
"test": "vitest"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Using Your Plugin in Projects
|
|
|
|
### Option 1: Plugin Inside the Monorepo
|
|
|
|
If developing within the elizaOS monorepo:
|
|
|
|
1. Add your plugin to the root `package.json` as a workspace dependency:
|
|
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"@yourorg/plugin-myplugin": "workspace:*"
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Run `bun install` in the root directory
|
|
|
|
3. Use the plugin in your project:
|
|
|
|
```typescript
|
|
import { myPlugin } from "@yourorg/plugin-myplugin";
|
|
|
|
const agent = {
|
|
name: "MyAgent",
|
|
plugins: [myPlugin],
|
|
};
|
|
```
|
|
|
|
### Option 2: Plugin Outside the Monorepo
|
|
|
|
For plugins outside the elizaOS monorepo:
|
|
|
|
1. In your plugin directory, build and link it:
|
|
|
|
```bash
|
|
# In your plugin directory
|
|
bun install
|
|
bun run build
|
|
bun link
|
|
```
|
|
|
|
2. In your project directory, link the plugin:
|
|
|
|
```bash
|
|
# In your project directory
|
|
cd packages/project-starter
|
|
bun link @yourorg/plugin-myplugin
|
|
```
|
|
|
|
3. Add to your project's `package.json`:
|
|
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"@yourorg/plugin-myplugin": "link:@yourorg/plugin-myplugin"
|
|
}
|
|
}
|
|
```
|
|
|
|
<Note>
|
|
When using `bun link`, remember to rebuild your plugin (`bun run build`) after
|
|
making changes for them to be reflected in your project.
|
|
</Note>
|
|
|
|
## Testing Plugins
|
|
|
|
### Test Environment Setup
|
|
|
|
#### Directory Structure
|
|
|
|
```
|
|
src/
|
|
__tests__/
|
|
test-utils.ts # Shared test utilities (real runtime helpers)
|
|
index.test.ts # Main plugin tests
|
|
actions.test.ts # Action tests
|
|
providers.test.ts # Provider tests
|
|
evaluators.test.ts # Evaluator tests
|
|
services.test.ts # Service tests
|
|
actions/
|
|
providers/
|
|
evaluators/
|
|
services/
|
|
index.ts
|
|
```
|
|
|
|
#### Base Test Imports
|
|
|
|
```typescript
|
|
import {
|
|
describe,
|
|
expect,
|
|
it,
|
|
vi,
|
|
beforeEach,
|
|
afterEach,
|
|
} from "vitest";
|
|
import {
|
|
type IAgentRuntime,
|
|
type Memory,
|
|
type State,
|
|
type HandlerCallback,
|
|
type Action,
|
|
type Provider,
|
|
type Evaluator,
|
|
ModelType,
|
|
logger,
|
|
} from "@elizaos/core";
|
|
```
|
|
|
|
### Creating Test Utilities
|
|
|
|
Create a `test-utils.ts` file with test helpers. All tests use **real runtime instances** with PGLite:
|
|
|
|
```typescript
|
|
import {
|
|
type IAgentRuntime,
|
|
type Memory,
|
|
type State,
|
|
type Character,
|
|
type UUID,
|
|
} from "@elizaos/core";
|
|
import { AgentRuntime } from "@elizaos/core";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
|
/**
|
|
* Creates a real AgentRuntime for testing with PGLite database.
|
|
* NO MOCKS - all tests use actual runtime infrastructure.
|
|
*/
|
|
export async function createTestRuntime(
|
|
characterOverrides?: Partial<Character>,
|
|
): Promise<IAgentRuntime> {
|
|
const agentId = uuidv4() as UUID;
|
|
const character: Character = {
|
|
id: agentId,
|
|
name: "TestAgent",
|
|
bio: "A test agent",
|
|
plugins: [],
|
|
settings: {},
|
|
...characterOverrides,
|
|
};
|
|
|
|
const runtime = new AgentRuntime({
|
|
agentId,
|
|
character,
|
|
logLevel: "error",
|
|
});
|
|
|
|
await runtime.initialize();
|
|
return runtime;
|
|
}
|
|
|
|
/**
|
|
* Cleans up a test runtime after tests complete
|
|
*/
|
|
export async function cleanupRuntime(runtime: IAgentRuntime): Promise<void> {
|
|
await runtime.stop();
|
|
}
|
|
|
|
/**
|
|
* Creates a test Memory object
|
|
*/
|
|
export function createTestMemory(overrides?: Partial<Memory>): Memory {
|
|
return {
|
|
id: uuidv4() as UUID,
|
|
entityId: uuidv4() as UUID,
|
|
roomId: uuidv4() as UUID,
|
|
content: {
|
|
text: "Test message",
|
|
...overrides?.content,
|
|
},
|
|
createdAt: Date.now(),
|
|
...overrides,
|
|
} as Memory;
|
|
}
|
|
|
|
/**
|
|
* Creates a test State object
|
|
*/
|
|
export function createTestState(overrides?: Partial<State>): State {
|
|
return {
|
|
values: {
|
|
test: "value",
|
|
...overrides?.values,
|
|
},
|
|
data: overrides?.data || {},
|
|
text: overrides?.text || "Test state",
|
|
} as State;
|
|
}
|
|
```
|
|
|
|
### Testing Actions
|
|
|
|
All tests use **real runtime instances** - no mocks:
|
|
|
|
```typescript
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
import { myAction } from "../src/actions/myAction";
|
|
import {
|
|
createTestRuntime,
|
|
cleanupRuntime,
|
|
createTestMemory,
|
|
createTestState,
|
|
} from "./test-utils";
|
|
import type { IAgentRuntime, Memory, State } from "@elizaos/core";
|
|
|
|
describe("MyAction", () => {
|
|
let runtime: IAgentRuntime;
|
|
let message: Memory;
|
|
let state: State;
|
|
|
|
beforeEach(async () => {
|
|
// Create real runtime with PGLite database
|
|
runtime = await createTestRuntime();
|
|
runtime.setSetting("MY_API_KEY", "test-key");
|
|
message = createTestMemory({ content: { text: "Do the thing" } });
|
|
state = createTestState();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await cleanupRuntime(runtime);
|
|
});
|
|
|
|
describe("validation", () => {
|
|
it("should validate when all requirements are met", async () => {
|
|
const isValid = await myAction.validate(runtime, message, state);
|
|
expect(isValid).toBe(true);
|
|
});
|
|
|
|
it("should not validate without required setting", async () => {
|
|
runtime.setSetting("MY_API_KEY", null);
|
|
const isValid = await myAction.validate(runtime, message, state);
|
|
expect(isValid).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("handler", () => {
|
|
it("should return success ActionResult on successful execution", async () => {
|
|
const callback = vi.fn();
|
|
|
|
const result = await myAction.handler(
|
|
runtime,
|
|
message,
|
|
state,
|
|
{},
|
|
callback,
|
|
);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.text).toContain("completed");
|
|
expect(result.values).toHaveProperty("lastActionTime");
|
|
expect(callback).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle errors gracefully", async () => {
|
|
// Remove required setting to trigger error
|
|
runtime.setSetting("MY_API_KEY", null);
|
|
|
|
const result = await myAction.handler(runtime, message, state);
|
|
|
|
expect(result.success).toBe(false);
|
|
expect(result.error).toBeDefined();
|
|
expect(result.text).toContain("Failed");
|
|
});
|
|
});
|
|
|
|
describe("examples", () => {
|
|
it("should have valid example structure", () => {
|
|
expect(myAction.examples).toBeDefined();
|
|
expect(Array.isArray(myAction.examples)).toBe(true);
|
|
|
|
// Each example should be a conversation array
|
|
for (const example of myAction.examples!) {
|
|
expect(Array.isArray(example)).toBe(true);
|
|
|
|
// Each message should have name and content
|
|
for (const message of example) {
|
|
expect(message).toHaveProperty("name");
|
|
expect(message).toHaveProperty("content");
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
### Testing Providers
|
|
|
|
```typescript
|
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import { myProvider } from "../src/providers/myProvider";
|
|
import {
|
|
createTestRuntime,
|
|
cleanupRuntime,
|
|
createTestMemory,
|
|
createTestState,
|
|
} from "./test-utils";
|
|
import type { IAgentRuntime, Memory, State } from "@elizaos/core";
|
|
|
|
describe("MyProvider", () => {
|
|
let runtime: IAgentRuntime;
|
|
let message: Memory;
|
|
let state: State;
|
|
|
|
beforeEach(async () => {
|
|
runtime = await createTestRuntime();
|
|
message = createTestMemory();
|
|
state = createTestState();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await cleanupRuntime(runtime);
|
|
});
|
|
|
|
it("should return provider result with text and data", async () => {
|
|
const result = await myProvider.get(runtime, message, state);
|
|
|
|
expect(result).toBeDefined();
|
|
expect(result.text).toContain("Current");
|
|
expect(result.data).toBeDefined();
|
|
expect(result.values).toBeDefined();
|
|
});
|
|
|
|
it("should handle missing data gracefully", async () => {
|
|
// Test with empty message content
|
|
message.content.text = "";
|
|
|
|
const result = await myProvider.get(runtime, message, state);
|
|
|
|
expect(result.text).toBeDefined();
|
|
});
|
|
});
|
|
```
|
|
|
|
### Testing Services
|
|
|
|
```typescript
|
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import { MyService } from "../src/services/myService";
|
|
import { createTestRuntime, cleanupRuntime } from "./test-utils";
|
|
import type { IAgentRuntime } from "@elizaos/core";
|
|
|
|
describe("MyService", () => {
|
|
let runtime: IAgentRuntime;
|
|
let service: MyService;
|
|
|
|
beforeEach(async () => {
|
|
runtime = await createTestRuntime();
|
|
runtime.setSetting("MY_API_KEY", "test-api-key");
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (service) {
|
|
await service.stop();
|
|
}
|
|
await cleanupRuntime(runtime);
|
|
});
|
|
|
|
it("should initialize successfully with valid config", async () => {
|
|
service = await MyService.start(runtime);
|
|
expect(service).toBeDefined();
|
|
expect(service.capabilityDescription).toBeDefined();
|
|
});
|
|
|
|
it("should throw error without API key", async () => {
|
|
runtime.setSetting("MY_API_KEY", null);
|
|
|
|
await expect(MyService.start(runtime)).rejects.toThrow(
|
|
"MY_API_KEY not configured",
|
|
);
|
|
});
|
|
|
|
it("should clean up resources on stop", async () => {
|
|
service = await MyService.start(runtime);
|
|
await service.stop();
|
|
// Verify cleanup happened
|
|
});
|
|
});
|
|
```
|
|
|
|
### E2E Testing
|
|
|
|
For integration testing with a live runtime:
|
|
|
|
```typescript
|
|
// tests/e2e/myPlugin.e2e.ts
|
|
export const myPluginE2ETests = {
|
|
name: "MyPlugin E2E Tests",
|
|
tests: [
|
|
{
|
|
name: "should execute full plugin flow",
|
|
fn: async (runtime: IAgentRuntime) => {
|
|
// Create test message
|
|
const message: Memory = {
|
|
id: generateId(),
|
|
entityId: "test-user",
|
|
roomId: runtime.agentId,
|
|
content: {
|
|
text: "Please do the thing",
|
|
source: "test",
|
|
},
|
|
};
|
|
|
|
// Store message
|
|
await runtime.createMemory(message, "messages");
|
|
|
|
// Compose state
|
|
const state = await runtime.composeState(message);
|
|
|
|
// Execute action
|
|
const result = await myAction.handler(
|
|
runtime,
|
|
message,
|
|
state,
|
|
{},
|
|
async (response) => {
|
|
// Verify callback responses
|
|
expect(response.text).toBeDefined();
|
|
},
|
|
);
|
|
|
|
// Verify result
|
|
expect(result.success).toBe(true);
|
|
|
|
// Verify side effects
|
|
const memories = await runtime.getMemories({
|
|
roomId: message.roomId,
|
|
tableName: "action_results",
|
|
count: 1,
|
|
});
|
|
|
|
expect(memories.length).toBeGreaterThan(0);
|
|
},
|
|
},
|
|
],
|
|
};
|
|
```
|
|
|
|
### Running Tests
|
|
|
|
```bash
|
|
# Run all tests
|
|
npx vitest
|
|
|
|
# Run specific test file
|
|
npx vitest src/__tests__/actions.test.ts
|
|
|
|
# Run with watch mode
|
|
npx vitest --watch
|
|
|
|
# Run with coverage
|
|
npx vitest --coverage
|
|
```
|
|
|
|
### Test Best Practices
|
|
|
|
1. **Use Real Runtime**: All tests use actual `AgentRuntime` with PGLite - no mocks
|
|
2. **Isolate Tests**: Use `beforeEach`/`afterEach` to create and cleanup runtime instances
|
|
3. **Test Happy Path and Errors**: Cover both success and failure cases
|
|
4. **Test Validation Logic**: Ensure actions validate correctly
|
|
5. **Test Examples**: Verify example structures are valid
|
|
6. **Test Side Effects**: Verify database writes with real database operations
|
|
7. **Use Descriptive Names**: Make test purposes clear
|
|
8. **Keep Tests Fast**: PGLite provides fast in-memory database
|
|
|
|
## Development Workflow
|
|
|
|
### 1. Development Mode
|
|
|
|
```bash
|
|
# Watch mode with hot reloading
|
|
bun run dev
|
|
|
|
# Run the generated development script when present
|
|
bun run dev
|
|
```
|
|
|
|
### 2. Building for Production
|
|
|
|
```bash
|
|
# Build the plugin
|
|
bun run build
|
|
|
|
# Output will be in dist/
|
|
```
|
|
|
|
### 3. Publishing
|
|
|
|
#### To npm
|
|
|
|
```bash
|
|
# Login to npm
|
|
npm login
|
|
|
|
# Publish
|
|
npm publish --access public
|
|
```
|
|
|
|
#### To GitHub Packages
|
|
|
|
Update `package.json`:
|
|
|
|
```json
|
|
{
|
|
"name": "@yourorg/plugin-name",
|
|
"publishConfig": {
|
|
"registry": "https://npm.pkg.github.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
Then publish:
|
|
|
|
```bash
|
|
npm publish
|
|
```
|
|
|
|
### 4. Version Management
|
|
|
|
```bash
|
|
# Bump version
|
|
npm version patch # 0.1.0 -> 0.1.1
|
|
npm version minor # 0.1.0 -> 0.2.0
|
|
npm version major # 0.1.0 -> 1.0.0
|
|
```
|
|
|
|
## Debugging
|
|
|
|
### Enable Debug Logging
|
|
|
|
```typescript
|
|
import { logger } from "@elizaos/core";
|
|
|
|
// In your plugin
|
|
logger.debug("Plugin initialized", { config });
|
|
logger.info("Action executed", { result });
|
|
logger.error("Failed to connect", { error });
|
|
```
|
|
|
|
### VS Code Debug Configuration
|
|
|
|
Create `.vscode/launch.json`:
|
|
|
|
```json
|
|
{
|
|
"version": "0.2.0",
|
|
"configurations": [
|
|
{
|
|
"type": "node",
|
|
"request": "launch",
|
|
"name": "Debug Plugin",
|
|
"runtimeExecutable": "bun",
|
|
"program": "${workspaceFolder}/src/index.ts",
|
|
"cwd": "${workspaceFolder}",
|
|
"console": "integratedTerminal"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Common Issues and Solutions
|
|
|
|
### Issue: Plugin not loading
|
|
|
|
**Solution**: Check that your plugin is properly exported and added to the agent's plugin array.
|
|
|
|
### Issue: TypeScript errors
|
|
|
|
**Solution**: Ensure `@elizaos/core` is installed and TypeScript is configured correctly.
|
|
|
|
### Issue: Service not available
|
|
|
|
**Solution**: Verify the service is registered in the plugin and started properly.
|
|
|
|
### Issue: Tests failing with module errors
|
|
|
|
**Solution**: Make sure your `tsconfig.json` has proper module resolution settings for Bun.
|
|
|
|
## See Also
|
|
|
|
<CardGroup cols={2}>
|
|
<Card title="Plugin Components" icon="cube" href="/plugins/architecture">
|
|
Deep dive into Actions, Providers, Evaluators, and Services
|
|
</Card>
|
|
|
|
<Card title="Common Patterns" icon="lightbulb" href="/plugins/patterns">
|
|
Learn proven plugin development patterns
|
|
</Card>
|
|
|
|
<Card title="Plugin Schemas" icon="list" href="/plugins/schemas">
|
|
Understand plugin configuration and validation
|
|
</Card>
|
|
|
|
<Card title="Plugin Reference" icon="book" href="/plugins/schemas">
|
|
Complete API reference for all interfaces
|
|
</Card>
|
|
|
|
<Card title="Publish a Plugin" icon="upload" href="/plugins/publish">
|
|
Share your plugin with the community
|
|
</Card>
|
|
|
|
<Card title="Deploy to Cloud" icon="cloud" href="/eliza-cloud-deployment">
|
|
Ship your agent with plugins to production
|
|
</Card>
|
|
</CardGroup>
|