Adding Agent Actions
The Starknet Agent Kit follows a decentralized approach where each contributor can add their own actions for a specific protocol. The goal is to make it easier to understand and scale to support the entire Network by clearly isolating the logic for each protocol.
--Tutorial video--
Folder Organization
Plugins Library (lib/agent/plugins/
)
Each protocol implementation should be placed in its own directory under lib/agent/plugins/
with the following structure:
lib/agent/plugins/
└── [protocol-name]/
├── abis/ # Protocol-specific ABIs (e.g., accounts, tokens, etc.)
├── actions/ # Implementations of protocol actions
├── utils/ # Utility functions and classes specific to the protocol
├── constant/ # Constants and addresses specific to the protocol
├── interface/ # Interfaces
├── schemas/ # Validation schemas
└── types/ # Type definitions specific to the protocol
For core actions such as RPC plugins or shared utilities, please add them
under lib/agent/plugins/core/
using the same underlying subdirectories.
For example, for a protocol named myProtocol
, the structure could look like:
lib/agent/plugins/myProtocol/
├── abis/
│ └── accountABI.json
├── actions/
│ └── myAction.ts
├── utils/
│ └── formatter.ts
├── constant/
│ └── networkConstants.ts
├── interface/
│ └── myActionSchema.ts
└── types/
└── myActionTypes.ts
Step-by-Step Implementation
Create the Validation Schema
Create a schema in lib/agent/plugins/myProtocol/schema/myActionSchema.ts
:
import { z } from "zod";
export const myActionSchema = z.object({
param1: z.string().describe("Description for the first parameter"),
param2: z.number().describe("Description for the second parameter"),
// Add more parameters as needed
});
Implement the Action
Place your action implementation in lib/agent/plugins/myProtocol/actions/myAction.ts
:
import { myActionSchema } from "../interface/myActionSchema";
export const myAction = async (
agent: StarknetAgentInterface,
params: z.infer<typeof myActionSchema>,
): Promise<string> => {
try {
// Protocol-specific implementation for myProtocol
const result = {}; // Replace with the actual logic
return JSON.stringify({
status: "success",
data: result,
});
} catch (error) {
return JSON.stringify({
status: "error",
error: error instanceof Error ? error.message : "Unknown error",
});
}
};
Add Utilities, Constants, or Types
- Place protocol-specific constants in
lib/agent/plugins/myProtocol/constant/
- Add helper functions in
lib/agent/plugins/myProtocol/utils/
- Define any additional types in
lib/agent/plugins/myProtocol/types/
Register Your Action
After implementing the action, register it so the agent can recognize and use it. For example:
import { StarknetToolRegistry } from "path/to/registry";
import { myActionSchema } from "../interface/myActionSchema";
import { myAction } from "../actions/myAction";
StarknetToolRegistry.registerTool({
name: "my_action",
description: "Description of what your action does",
schema: myActionSchema,
execute: myAction,
});
Test Your Action
Ensure your action works as expected by writing tests. Create a test file in test/unit-test/plugins/myProtocol/myAction.spec.ts
:
// test/unit-test/plugins/myProtocol/myAction.spec.ts
describe("myAction", () => {
let agent: StarknetAgentInterface;
beforeEach(() => {
agent = createMockAgent();
});
it("should execute successfully", async () => {
const result = await myAction(agent, {
param1: "test",
param2: 123,
});
expect(JSON.parse(result).status).toBe("success");
});
});
Best Practices
Tool Organization
Follow these organizational principles:
- Place tools in
src/lib/agent/plugins/[protocol-name]/
- Use consistent naming:
- plugins: camelCase
- Types: PascalCase
- Files: kebab-case
- One tool per file
- Group related tools logically