Adding Agent Actions

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