Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions typescript/framework-extensions/mastra/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"parser": "@typescript-eslint/parser",
"extends": ["../../.eslintrc.base.json"]
}
7 changes: 7 additions & 0 deletions typescript/framework-extensions/mastra/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
docs/
dist/
coverage/
.github/
src/client
**/**/*.json
*.md
11 changes: 11 additions & 0 deletions typescript/framework-extensions/mastra/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 100,
"proseWrap": "never"
}
67 changes: 67 additions & 0 deletions typescript/framework-extensions/mastra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Coinbase Agentkit Extension - Mastra

This package is an extension used to easily plug [AgentKit](https://docs.cdp.coinbase.com/agentkit/docs/welcome) into [Mastra](https://mastra.ai/), the TypeScript AI agent framework.

## Installation

For a single command to install all necessary dependencies, run:

```bash
npm install @coinbase/agentkit-mastra @coinbase/agentkit @mastra/core @ai-sdk/openai
```

To break it down, this package is:

```bash
npm install @coinbase/agentkit-mastra
```

This package is used alongside AgentKit and Mastra, so these will need to be installed as well.

```bash
npm install @coinbase/agentkit @mastra/core
```

Finally, install the model provider you want to use. For example, to use OpenAI, install the `@ai-sdk/openai` package. See [here](https://mastra.ai/docs) for more information on Mastra's supported model providers.

```bash
npm install @ai-sdk/openai
```

## Usage

The main export of this package is the `getMastraTools` function. This function takes an AgentKit instance and returns a record of Mastra-compatible tools. These tools can then be passed directly to a Mastra agent.

Here's a snippet of code that shows how to use the `getMastraTools` function to get the tools for the AgentKit agent.

###### agent.ts

```typescript
import { getMastraTools } from "@coinbase/agentkit-mastra";
import { AgentKit } from "@coinbase/agentkit";
import { Agent } from "@mastra/core/agent";
import { openai } from "@ai-sdk/openai";

// Get your Coinbase Developer Platform API key from the Portal: https://portal.cdp.coinbase.com/
// Or, check out one of the other supported wallet providers: https://github.com/coinbase/agentkit/tree/main/typescript/agentkit
const agentKit = await AgentKit.from({
cdpApiKeyId: process.env.CDP_API_KEY_ID,
cdpApiKeySecret: process.env.CDP_API_KEY_SECRET,
});

const tools = getMastraTools(agentKit);

const agent = new Agent({
name: "Onchain Agent",
instructions: "You are an onchain AI assistant with access to a wallet.",
model: openai("gpt-4o-mini"), // Make sure to have OPENAI_API_KEY set in your environment variables
tools,
});

const response = await agent.generate("Print wallet details");
console.log(response.text);
```

## Contributing

We welcome contributions of all kinds! Please see our [Contributing Guide](https://github.com/coinbase/agentkit/blob/main/CONTRIBUTING.md) for detailed setup instructions and contribution guidelines.
8 changes: 8 additions & 0 deletions typescript/framework-extensions/mastra/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const baseConfig = require("../../jest.config.base.cjs");

module.exports = {
...baseConfig,
setupFilesAfterEnv: ["<rootDir>/setup-jest.js"],
coveragePathIgnorePatterns: ["node_modules", "dist", "docs", "index.ts"],
coverageThreshold: {},
};
49 changes: 49 additions & 0 deletions typescript/framework-extensions/mastra/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@coinbase/agentkit-mastra",
"version": "0.1.0",
"description": "Mastra framework extension of CDP Agentkit",
"repository": "https://github.com/coinbase/agentkit",
"author": "Coinbase Inc.",
"license": "Apache-2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"lint": "eslint -c .eslintrc.json \"src/**/*.ts\"",
"lint:fix": "eslint -c .eslintrc.json \"src/**/*.ts\" --fix",
"format": "prettier -c .prettierrc --write \"**/*.{ts,js,cjs,json,md}\"",
"format:check": "prettier -c .prettierrc --check \"**/*.{ts,js,cjs,json,md}\"",
"check": "tsc --noEmit",
"test": "jest --no-cache --testMatch='**/*.test.ts'",
"clean": "rm -rf dist/*",
"prepack": "tsc",
"docs": "typedoc --entryPoints ./src --entryPointStrategy expand --exclude ./src/tests/**/*.ts",
"docs:serve": "http-server ./docs",
"dev": "tsc --watch"
},
"keywords": [
"coinbase",
"sdk",
"crypto",
"cdp",
"agentkit",
"ai",
"agent",
"nodejs",
"typescript",
"mastra"
],
"dependencies": {
"zod": "^4.0.0"
},
"devDependencies": {
"@coinbase/agentkit": "workspace:*"
},
"peerDependencies": {
"@coinbase/agentkit": ">=0.1.0",
"@mastra/core": ">=1.0.0"
}
}
1 change: 1 addition & 0 deletions typescript/framework-extensions/mastra/setup-jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jest.mock("jose", () => ({}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { z } from "zod";
import { getMastraTools } from "./getMastraTools";
import { AgentKit } from "@coinbase/agentkit";

// Mock AgentKit before importing - this prevents loading ES-only dependencies
jest.mock("@coinbase/agentkit", () => ({
AgentKit: {
from: jest.fn(),
},
}));

// Mock @mastra/core/tools
jest.mock("@mastra/core/tools", () => ({
createTool: jest.fn((config: Record<string, unknown>) => ({
id: config.id,
description: config.description,
inputSchema: config.inputSchema,
execute: config.execute,
})),
}));

// Define mock action after imports
const mockAction = {
name: "testAction",
description: "A test action",
schema: z.object({ test: z.string() }),
invoke: jest.fn(async (arg: { test: string }) => `Invoked with ${arg.test}`),
};

// Configure the mock
(AgentKit.from as jest.Mock).mockImplementation(() => ({
getActions: jest.fn(() => [mockAction]),
}));

describe("getMastraTools", () => {
it("should return a record of tools with correct properties", async () => {
const mockAgentKit = await AgentKit.from({});
const tools = getMastraTools(mockAgentKit);

expect(tools).toHaveProperty("testAction");
const tool = tools.testAction;

expect(tool.id).toBe(mockAction.name);
expect(tool.description).toBe(mockAction.description);
expect(tool.inputSchema).toBe(mockAction.schema);

// Test execution
const result = await tool.execute!({ test: "data" });
expect(result).toBe("Invoked with data");
});

it("should handle multiple actions", async () => {
const secondAction = {
name: "secondAction",
description: "A second test action",
schema: z.object({ value: z.number() }),
invoke: jest.fn(async (arg: { value: number }) => `Value is ${arg.value}`),
};

(AgentKit.from as jest.Mock).mockImplementation(() => ({
getActions: jest.fn(() => [mockAction, secondAction]),
}));

const mockAgentKit = await AgentKit.from({});
const tools = getMastraTools(mockAgentKit);

expect(Object.keys(tools)).toHaveLength(2);
expect(tools).toHaveProperty("testAction");
expect(tools).toHaveProperty("secondAction");

const result = await tools.secondAction.execute!({ value: 42 });
expect(result).toBe("Value is 42");
});

it("should return an empty record when no actions are available", async () => {
(AgentKit.from as jest.Mock).mockImplementation(() => ({
getActions: jest.fn(() => []),
}));

const mockAgentKit = await AgentKit.from({});
const tools = getMastraTools(mockAgentKit);

expect(Object.keys(tools)).toHaveLength(0);
});
});
34 changes: 34 additions & 0 deletions typescript/framework-extensions/mastra/src/getMastraTools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Main exports for the CDP Mastra package
*/

import { z } from "zod";
import { AgentKit, type Action } from "@coinbase/agentkit";
import { createTool } from "@mastra/core/tools";

/**
* The return type of getMastraTools - a record mapping action names to Mastra tools
*/
export type MastraToolSet = Record<string, ReturnType<typeof createTool>>;

/**
* Get Mastra tools from an AgentKit instance
*
* @param agentKit - The AgentKit instance
* @returns A record of Mastra tools keyed by action name, compatible with Mastra agents
*/
export function getMastraTools(agentKit: AgentKit): MastraToolSet {
const actions: Action[] = agentKit.getActions();
return actions.reduce<MastraToolSet>((acc, action) => {
acc[action.name] = createTool({
id: action.name,
description: action.description,
inputSchema: action.schema,
execute: async (arg: z.output<typeof action.schema>) => {
const result = await action.invoke(arg);
return result;
},
});
return acc;
}, {});
}
1 change: 1 addition & 0 deletions typescript/framework-extensions/mastra/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./getMastraTools";
11 changes: 11 additions & 0 deletions typescript/framework-extensions/mastra/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "nodenext",
"isolatedModules": true
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts"]
}
Loading
Loading