From 48cbf02dcd32782f2fb99a738d144fb9c38004e7 Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 10:17:33 -0400 Subject: [PATCH 01/10] add action provider --- .../agentkit/src/action-providers/index.ts | 1 + .../src/action-providers/messari/index.ts | 2 + .../messari/messariActionProvider.ts | 158 ++++++++++++++++++ .../src/action-providers/messari/schemas.ts | 14 ++ 4 files changed, 175 insertions(+) create mode 100644 typescript/agentkit/src/action-providers/messari/index.ts create mode 100644 typescript/agentkit/src/action-providers/messari/messariActionProvider.ts create mode 100644 typescript/agentkit/src/action-providers/messari/schemas.ts diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index a4e9453dd..60bad1ecd 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -11,6 +11,7 @@ export * from "./erc20"; export * from "./erc721"; export * from "./farcaster"; export * from "./jupiter"; +export * from "./messari"; export * from "./pyth"; export * from "./moonwell"; export * from "./morpho"; diff --git a/typescript/agentkit/src/action-providers/messari/index.ts b/typescript/agentkit/src/action-providers/messari/index.ts new file mode 100644 index 000000000..d01209430 --- /dev/null +++ b/typescript/agentkit/src/action-providers/messari/index.ts @@ -0,0 +1,2 @@ +export * from "./messariActionProvider"; +export * from "./schemas"; diff --git a/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts new file mode 100644 index 000000000..714dd9866 --- /dev/null +++ b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts @@ -0,0 +1,158 @@ +import { z } from "zod"; +import { ActionProvider } from "../actionProvider"; +import { CreateAction } from "../actionDecorator"; +import { Network } from "../../network"; +import { MessariResearchQuestionSchema } from "./schemas"; + +/** + * Configuration options for the MessariActionProvider. + */ +export interface MessariActionProviderConfig { + /** + * Messari API Key + */ + apiKey?: string; +} + +// Configuration constants +const CONFIG = { + API_ENDPOINT: "https://api.messari.io/ai/v1/chat/completions", +}; + +/** + * Types for API responses and errors + */ +interface MessariAPIResponse { + data: { + messages: Array<{ + content: string; + role: string; + }>; + }; +} + +interface MessariError extends Error { + status?: number; + statusText?: string; + responseText?: string; +} + +/** + * MessariActionProvider is an action provider for Messari AI toolkit interactions. + * It enables AI agents to ask research questions about crypto markets, protocols, and tokens. + * + * @augments ActionProvider + */ +export class MessariActionProvider extends ActionProvider { + private readonly apiKey: string; + + /** + * Constructor for the MessariActionProvider class. + * + * @param config - The configuration options for the MessariActionProvider + */ + constructor(config: MessariActionProviderConfig = {}) { + super("messari", []); + + config.apiKey ||= process.env.MESSARI_API_KEY; + + if (!config.apiKey) { + throw new Error("MESSARI_API_KEY is not configured."); + } + + this.apiKey = config.apiKey; + } + + /** + * Makes a request to the Messari AI API with a research question + * + * @param args - The arguments containing the research question + * @returns A string containing the research results or an error message + */ + @CreateAction({ + name: "research_question", + description: ` +This tool will query the Messari AI toolkit with a research question about crypto markets, protocols, or tokens. + +Use this tool for questions like: +1. Market data, statistics, or metrics +2. Rankings or comparisons +3. Historical data or trends +4. Information about specific protocols, tokens, or platforms +5. Financial analysis or performance data + +A successful response will return the research findings from Messari. +A failure response will return an error message with details. + +Examples of good questions: +- "What are the top 10 L2s by fees?" +- "What is the current price of ETH?" +- "What is the TVL of Arbitrum?" +- "How has Bitcoin's market cap changed over the last month?" + `, + schema: MessariResearchQuestionSchema, + }) + async researchQuestion(args: z.infer): Promise { + try { + const response = await fetch(CONFIG.API_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/json", + "x-messari-api-key": this.apiKey, + }, + body: JSON.stringify({ + messages: [ + { + role: "user", + content: args.question, + }, + ], + }), + }); + + if (!response.ok) { + const responseText = await response.text(); + const error = new Error() as MessariError; + error.status = response.status; + error.statusText = response.statusText; + error.responseText = responseText; + throw error; + } + + const data = (await response.json()) as MessariAPIResponse; + const result = data.data.messages[0].content; + + return `Messari Research Results:\n\n${result}`; + } catch (error) { + const err = error as MessariError; + const errorDetails = { + status: err.status, + statusText: err.statusText, + responseText: err.responseText, + message: err.message, + }; + + return `Error querying Messari AI: ${JSON.stringify(errorDetails, null, 2)}`; + } + } + + /** + * Checks if the action provider supports the given network. + * Messari research is network-agnostic, so it supports all networks. + * + * @param _ - The network to check + * @returns Always returns true as Messari research is network-agnostic + */ + supportsNetwork(_: Network): boolean { + return true; // Messari research is network-agnostic + } +} + +/** + * Factory function to create a new MessariActionProvider instance. + * + * @param config - The configuration options for the MessariActionProvider + * @returns A new instance of MessariActionProvider + */ +export const messariActionProvider = (config: MessariActionProviderConfig = {}) => + new MessariActionProvider(config); diff --git a/typescript/agentkit/src/action-providers/messari/schemas.ts b/typescript/agentkit/src/action-providers/messari/schemas.ts new file mode 100644 index 000000000..24f17fd69 --- /dev/null +++ b/typescript/agentkit/src/action-providers/messari/schemas.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +/** + * Input schema for submitting a research question to Messari AI. + */ +export const MessariResearchQuestionSchema = z + .object({ + question: z + .string() + .min(1, "Research question is required.") + .describe("The research question about crypto markets, protocols, or tokens"), + }) + .strip() + .describe("Input schema for submitting a research question to Messari AI"); From d74046893c602490c28af6a40fc89856b6284821 Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 11:50:40 -0400 Subject: [PATCH 02/10] Add Messari action provider with tests --- .../messari/messariActionProvider.test.ts | 132 ++++++++++++++++++ .../examples/langchain-cdp-chatbot/chatbot.ts | 27 +++- 2 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts diff --git a/typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts b/typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts new file mode 100644 index 000000000..6089f0b27 --- /dev/null +++ b/typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts @@ -0,0 +1,132 @@ +import { messariActionProvider, MessariActionProvider } from "./messariActionProvider"; + +const MOCK_API_KEY = "messari-test-key"; + +// Sample response for the research question action +const MOCK_RESEARCH_RESPONSE = { + data: { + messages: [ + { + role: "assistant", + content: + "Ethereum (ETH) has shown strong performance over the past month with a 15% price increase. The current price is approximately $3,500, up from $3,000 at the beginning of the month. Trading volume has also increased by 20% in the same period.", + }, + ], + }, +}; + +describe("MessariActionProvider", () => { + let provider: MessariActionProvider; + + beforeEach(() => { + process.env.MESSARI_API_KEY = MOCK_API_KEY; + provider = messariActionProvider({ apiKey: MOCK_API_KEY }); + jest.restoreAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + delete process.env.MESSARI_API_KEY; + }); + + describe("constructor", () => { + it("should initialize with API key from constructor", () => { + const customProvider = messariActionProvider({ apiKey: "custom-key" }); + expect(customProvider["apiKey"]).toBe("custom-key"); + }); + + it("should initialize with API key from environment variable", () => { + process.env.MESSARI_API_KEY = "env-key"; + const envProvider = messariActionProvider(); + expect(envProvider["apiKey"]).toBe("env-key"); + }); + + it("should throw error if API key is not provided", () => { + delete process.env.MESSARI_API_KEY; + expect(() => messariActionProvider()).toThrow("MESSARI_API_KEY is not configured."); + }); + }); + + describe("researchQuestion", () => { + it("should successfully fetch research results", async () => { + const fetchMock = jest.spyOn(global, "fetch").mockResolvedValue({ + ok: true, + json: async () => MOCK_RESEARCH_RESPONSE, + } as Response); + + const question = "What is the current price of Ethereum?"; + const response = await provider.researchQuestion({ question }); + + // Verify the API was called with the correct parameters + expect(fetchMock).toHaveBeenCalled(); + const [url, options] = fetchMock.mock.calls[0]; + + // Check URL + expect(url).toBe("https://api.messari.io/ai/v1/chat/completions"); + + // Check request options + expect(options).toEqual( + expect.objectContaining({ + method: "POST", + headers: expect.objectContaining({ + "Content-Type": "application/json", + "x-messari-api-key": MOCK_API_KEY, + }), + body: JSON.stringify({ + messages: [ + { + role: "user", + content: question, + }, + ], + }), + }), + ); + + // Check response formatting + expect(response).toContain("Messari Research Results:"); + expect(response).toContain(MOCK_RESEARCH_RESPONSE.data.messages[0].content); + }); + + it("should handle non-ok response", async () => { + const statusText = "Too Many Requests"; + const responseText = "Rate limit exceeded"; + + jest.spyOn(global, "fetch").mockResolvedValue({ + ok: false, + status: 429, + statusText, + text: async () => responseText, + } as Response); + + const response = await provider.researchQuestion({ + question: "What is the current price of Bitcoin?", + }); + + expect(response).toContain("Error querying Messari AI"); + expect(response).toContain("429"); + expect(response).toContain(statusText); + expect(response).toContain(responseText); + }); + + it("should handle fetch error", async () => { + const error = new Error("Network error"); + jest.spyOn(global, "fetch").mockRejectedValue(error); + + const response = await provider.researchQuestion({ + question: "What is the market cap of Solana?", + }); + + expect(response).toContain("Error querying Messari AI"); + expect(response).toContain(error.message); + }); + }); + + describe("supportsNetwork", () => { + it("should always return true as research is network-agnostic", () => { + expect(provider.supportsNetwork({ protocolFamily: "evm" })).toBe(true); + expect(provider.supportsNetwork({ protocolFamily: "solana" })).toBe(true); + expect(provider.supportsNetwork({ protocolFamily: "unknown" })).toBe(true); + }); + }); +}); diff --git a/typescript/examples/langchain-cdp-chatbot/chatbot.ts b/typescript/examples/langchain-cdp-chatbot/chatbot.ts index 182c82517..57ba5c286 100644 --- a/typescript/examples/langchain-cdp-chatbot/chatbot.ts +++ b/typescript/examples/langchain-cdp-chatbot/chatbot.ts @@ -10,6 +10,7 @@ import { pythActionProvider, openseaActionProvider, alloraActionProvider, + messariActionProvider, } from "@coinbase/agentkit"; import { getLangChainTools } from "@coinbase/agentkit-langchain"; import { HumanMessage } from "@langchain/core/messages"; @@ -32,7 +33,12 @@ function validateEnvironment(): void { const missingVars: string[] = []; // Check required variables - const requiredVars = ["OPENAI_API_KEY", "CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"]; + const requiredVars = [ + "OPENAI_API_KEY", + "CDP_API_KEY_NAME", + "CDP_API_KEY_PRIVATE_KEY", + "MESSARI_API_KEY", + ]; requiredVars.forEach(varName => { if (!process.env[varName]) { missingVars.push(varName); @@ -122,6 +128,7 @@ async function initializeAgent() { ] : []), alloraActionProvider(), + messariActionProvider(), ], }); @@ -141,12 +148,18 @@ async function initializeAgent() { empowered to interact onchain using your tools. If you ever need funds, you can request them from the faucet if you are on network ID 'base-sepolia'. If not, you can provide your wallet details and request funds from the user. Before executing your first action, get the wallet details to see what network - you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone - asks you to do something you can't do with your currently available tools, you must say so, and - encourage them to implement it themselves using the CDP SDK + Agentkit, recommend they go to - docs.cdp.coinbase.com for more information. Be concise and helpful with your responses. Refrain from - restating your tools' descriptions unless it is explicitly requested. - `, + you are on and what your wallet address is. + + You can also access crypto market research data through the Messari AI toolkit. You can ask questions about: + - Market data, statistics, or metrics + - Rankings or comparisons + - Historical data or trends + - Information about specific protocols, tokens, or platforms + - Financial analysis or performance data + + Be concise and helpful with your responses. + Refrain from restating your tools' descriptions unless it is explicitly requested. + `, }); // Save wallet data From 4895e4c6cf571db0831e28aaa59550fe2129f19c Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 12:06:50 -0400 Subject: [PATCH 03/10] revert chatbot.ts example --- .../examples/langchain-cdp-chatbot/chatbot.ts | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/typescript/examples/langchain-cdp-chatbot/chatbot.ts b/typescript/examples/langchain-cdp-chatbot/chatbot.ts index 57ba5c286..182c82517 100644 --- a/typescript/examples/langchain-cdp-chatbot/chatbot.ts +++ b/typescript/examples/langchain-cdp-chatbot/chatbot.ts @@ -10,7 +10,6 @@ import { pythActionProvider, openseaActionProvider, alloraActionProvider, - messariActionProvider, } from "@coinbase/agentkit"; import { getLangChainTools } from "@coinbase/agentkit-langchain"; import { HumanMessage } from "@langchain/core/messages"; @@ -33,12 +32,7 @@ function validateEnvironment(): void { const missingVars: string[] = []; // Check required variables - const requiredVars = [ - "OPENAI_API_KEY", - "CDP_API_KEY_NAME", - "CDP_API_KEY_PRIVATE_KEY", - "MESSARI_API_KEY", - ]; + const requiredVars = ["OPENAI_API_KEY", "CDP_API_KEY_NAME", "CDP_API_KEY_PRIVATE_KEY"]; requiredVars.forEach(varName => { if (!process.env[varName]) { missingVars.push(varName); @@ -128,7 +122,6 @@ async function initializeAgent() { ] : []), alloraActionProvider(), - messariActionProvider(), ], }); @@ -148,18 +141,12 @@ async function initializeAgent() { empowered to interact onchain using your tools. If you ever need funds, you can request them from the faucet if you are on network ID 'base-sepolia'. If not, you can provide your wallet details and request funds from the user. Before executing your first action, get the wallet details to see what network - you are on and what your wallet address is. - - You can also access crypto market research data through the Messari AI toolkit. You can ask questions about: - - Market data, statistics, or metrics - - Rankings or comparisons - - Historical data or trends - - Information about specific protocols, tokens, or platforms - - Financial analysis or performance data - - Be concise and helpful with your responses. - Refrain from restating your tools' descriptions unless it is explicitly requested. - `, + you're on. If there is a 5XX (internal) HTTP error code, ask the user to try again later. If someone + asks you to do something you can't do with your currently available tools, you must say so, and + encourage them to implement it themselves using the CDP SDK + Agentkit, recommend they go to + docs.cdp.coinbase.com for more information. Be concise and helpful with your responses. Refrain from + restating your tools' descriptions unless it is explicitly requested. + `, }); // Save wallet data From 936610465b731f696c7689c9b6daa712bddbe641 Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 12:21:06 -0400 Subject: [PATCH 04/10] add changelog --- typescript/.changeset/ninety-gifts-know.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 typescript/.changeset/ninety-gifts-know.md diff --git a/typescript/.changeset/ninety-gifts-know.md b/typescript/.changeset/ninety-gifts-know.md new file mode 100644 index 000000000..8607e82f4 --- /dev/null +++ b/typescript/.changeset/ninety-gifts-know.md @@ -0,0 +1,5 @@ +--- +"@coinbase/agentkit": patch +--- + +Add a new Messari action provider that enables AI agents to query the Messari AI toolkit for crypto market research data. From 05905135533b33d011f60b4eb75cf5a8becff5d2 Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 12:22:07 -0400 Subject: [PATCH 05/10] Add README for Messari action provider --- .../src/action-providers/messari/README.md | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 typescript/agentkit/src/action-providers/messari/README.md diff --git a/typescript/agentkit/src/action-providers/messari/README.md b/typescript/agentkit/src/action-providers/messari/README.md new file mode 100644 index 000000000..bafadbfa4 --- /dev/null +++ b/typescript/agentkit/src/action-providers/messari/README.md @@ -0,0 +1,78 @@ +# Messari Action Provider + +The Messari Action Provider enables AI agents to query the [Messari AI toolkit](https://messari.io/) for crypto market research data. This provider allows agents to ask research questions about market data, statistics, rankings, historical trends, and information about specific protocols, tokens, or platforms. + +## Configuration + +To use the Messari Action Provider, you need to obtain a Messari API key. You can configure the provider in two ways: + +### 1. Environment Variable + +Set the `MESSARI_API_KEY` environment variable: + +```bash +MESSARI_API_KEY=your_messari_api_key +``` + +### 2. Direct Configuration + +Pass the API key directly when initializing the provider: + +```typescript +import { messariActionProvider } from "@coinbase/agentkit"; + +const provider = messariActionProvider({ + apiKey: "your_messari_api_key", +}); +``` + +## Actions + +### `research_question` + +This action allows the agent to query the Messari AI toolkit with a research question about crypto markets, protocols, or tokens. + +#### Input Schema + +| Parameter | Type | Description | +|-----------|--------|--------------------------------------------------------------| +| question | string | The research question about crypto markets, protocols, or tokens | + +#### Example Usage + +```typescript +import { AgentKit, messariActionProvider } from "@coinbase/agentkit"; +import { getLangChainTools } from "@coinbase/agentkit-langchain"; +import { createReactAgent } from "@langchain/langgraph/prebuilt"; +import { ChatOpenAI } from "@langchain/openai"; + +// Initialize AgentKit with the Messari action provider +const agentkit = await AgentKit.from({ + actionProviders: [messariActionProvider()], +}); + +// Get LangChain tools from AgentKit +const tools = await getLangChainTools(agentkit); + +// Create a LangChain agent with the tools +const llm = new ChatOpenAI({ model: "gpt-4o-mini" }); +const agent = createReactAgent({ + llm, + tools, +}); + +// The agent can now use the Messari research_question action +// Example prompt: "What is the current price of Ethereum?" +``` + +#### Example Response + +``` +Messari Research Results: + +Ethereum (ETH) has shown strong performance over the past month with a 15% price increase. The current price is approximately $3,500, up from $3,000 at the beginning of the month. Trading volume has also increased by 20% in the same period. +``` + +## Network Support + +The Messari Action Provider is network-agnostic, meaning it supports all networks. The research capabilities are not tied to any specific blockchain network. \ No newline at end of file From c92aeb70377505bff5b189e912217938a103387e Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 16:57:55 -0400 Subject: [PATCH 06/10] clean up error handling --- .../messari/messariActionProvider.ts | 124 ++++++++++++++---- 1 file changed, 99 insertions(+), 25 deletions(-) diff --git a/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts index 714dd9866..54aa721a5 100644 --- a/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts +++ b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts @@ -14,11 +14,6 @@ export interface MessariActionProviderConfig { apiKey?: string; } -// Configuration constants -const CONFIG = { - API_ENDPOINT: "https://api.messari.io/ai/v1/chat/completions", -}; - /** * Types for API responses and errors */ @@ -31,10 +26,19 @@ interface MessariAPIResponse { }; } +interface MessariErrorResponse { + error?: string; + data?: null | unknown; +} + interface MessariError extends Error { status?: number; statusText?: string; responseText?: string; + errorResponse?: { + error?: string; + data?: null | unknown; + }; } /** @@ -45,6 +49,7 @@ interface MessariError extends Error { */ export class MessariActionProvider extends ActionProvider { private readonly apiKey: string; + private readonly baseUrl = "https://api.messari.io/ai/v1"; /** * Constructor for the MessariActionProvider class. @@ -94,7 +99,8 @@ Examples of good questions: }) async researchQuestion(args: z.infer): Promise { try { - const response = await fetch(CONFIG.API_ENDPOINT, { + // Make API request + const response = await fetch(`${this.baseUrl}/chat/completions`, { method: "POST", headers: { "Content-Type": "application/json", @@ -109,30 +115,31 @@ Examples of good questions: ], }), }); - if (!response.ok) { - const responseText = await response.text(); - const error = new Error() as MessariError; - error.status = response.status; - error.statusText = response.statusText; - error.responseText = responseText; - throw error; + throw await this.createMessariError(response); } - const data = (await response.json()) as MessariAPIResponse; - const result = data.data.messages[0].content; + // Parse and validate response + let data: MessariAPIResponse; + try { + data = (await response.json()) as MessariAPIResponse; + } catch (jsonError) { + throw new Error( + `Failed to parse API response: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`, + ); + } + if (!data.data?.messages?.[0]?.content) { + throw new Error("Received invalid response format from Messari API"); + } + const result = data.data.messages[0].content; return `Messari Research Results:\n\n${result}`; - } catch (error) { - const err = error as MessariError; - const errorDetails = { - status: err.status, - statusText: err.statusText, - responseText: err.responseText, - message: err.message, - }; - - return `Error querying Messari AI: ${JSON.stringify(errorDetails, null, 2)}`; + } catch (error: unknown) { + if (error instanceof Error && "responseText" in error) { + return this.formatMessariApiError(error as MessariError); + } + + return this.formatGenericError(error); } } @@ -146,6 +153,73 @@ Examples of good questions: supportsNetwork(_: Network): boolean { return true; // Messari research is network-agnostic } + + /** + * Creates a MessariError from an HTTP response + * + * @param response - The fetch Response object + * @returns A MessariError with response details + */ + private async createMessariError(response: Response): Promise { + const error = new Error( + `Messari API returned ${response.status} ${response.statusText}`, + ) as MessariError; + error.status = response.status; + error.statusText = response.statusText; + + const responseText = await response.text(); + error.responseText = responseText; + try { + const errorJson = JSON.parse(responseText) as MessariErrorResponse; + error.errorResponse = errorJson; + } catch { + // If parsing fails, just use the raw text + } + + return error; + } + + /** + * Formats error details for API errors + * + * @param error - The MessariError to format + * @returns Formatted error message + */ + private formatMessariApiError(error: MessariError): string { + if (error.errorResponse?.error) { + return `Messari API Error: ${error.errorResponse.error}`; + } + + const errorDetails = { + status: error.status, + statusText: error.statusText, + responseText: error.responseText, + message: error.message, + }; + return `Messari API Error: ${JSON.stringify(errorDetails, null, 2)}`; + } + + /** + * Formats generic errors + * + * @param error - The error to format + * @returns Formatted error message + */ + private formatGenericError(error: unknown): string { + // Check if this might be a JSON string containing an error message + if (typeof error === "string") { + try { + const parsedError = JSON.parse(error) as MessariErrorResponse; + if (parsedError.error) { + return `Messari API Error: ${parsedError.error}`; + } + } catch { + // Not valid JSON, continue with normal handling + } + } + + return `Unexpected error: ${error instanceof Error ? error.message : String(error)}`; + } } /** From 2c35705acdf2c8743942073ae9cb9349eab443eb Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 17:52:40 -0400 Subject: [PATCH 07/10] update tests --- .../messari/messariActionProvider.test.ts | 88 ++++++++++++++++--- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts b/typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts index 6089f0b27..c78aa811a 100644 --- a/typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/messari/messariActionProvider.test.ts @@ -15,6 +15,12 @@ const MOCK_RESEARCH_RESPONSE = { }, }; +// Sample error response in Messari format +const MOCK_ERROR_RESPONSE = { + error: "Internal server error, please try again. If the problem persists, please contact support", + data: null, +}; + describe("MessariActionProvider", () => { let provider: MessariActionProvider; @@ -88,25 +94,75 @@ describe("MessariActionProvider", () => { expect(response).toContain(MOCK_RESEARCH_RESPONSE.data.messages[0].content); }); - it("should handle non-ok response", async () => { - const statusText = "Too Many Requests"; - const responseText = "Rate limit exceeded"; + it("should handle non-ok response with structured error format", async () => { + const errorResponseText = JSON.stringify(MOCK_ERROR_RESPONSE); + + jest.spyOn(global, "fetch").mockResolvedValue({ + ok: false, + status: 500, + statusText: "Internal Server Error", + text: async () => errorResponseText, + } as Response); + + const response = await provider.researchQuestion({ + question: "What is the current price of Bitcoin?", + }); + + // Should use the structured error message from the response + expect(response).toContain("Messari API Error: Internal server error"); + expect(response).not.toContain("500"); // Should not include technical details when we have a structured error + }); + + it("should handle non-ok response with non-JSON error format", async () => { + const plainTextError = "Rate limit exceeded"; jest.spyOn(global, "fetch").mockResolvedValue({ ok: false, status: 429, - statusText, - text: async () => responseText, + statusText: "Too Many Requests", + text: async () => plainTextError, } as Response); const response = await provider.researchQuestion({ question: "What is the current price of Bitcoin?", }); - expect(response).toContain("Error querying Messari AI"); + // Should fall back to detailed error format + expect(response).toContain("Messari API Error:"); expect(response).toContain("429"); - expect(response).toContain(statusText); - expect(response).toContain(responseText); + expect(response).toContain("Too Many Requests"); + expect(response).toContain(plainTextError); + }); + + it("should handle JSON parsing error in successful response", async () => { + jest.spyOn(global, "fetch").mockResolvedValue({ + ok: true, + json: async () => { + throw new Error("Invalid JSON"); + }, + } as unknown as Response); + + const response = await provider.researchQuestion({ + question: "What is the market cap of Solana?", + }); + + expect(response).toContain("Unexpected error: Failed to parse API response"); + expect(response).toContain("Invalid JSON"); + }); + + it("should handle invalid response format", async () => { + jest.spyOn(global, "fetch").mockResolvedValue({ + ok: true, + json: async () => ({ data: { messages: [] } }), // Empty messages array + } as Response); + + const response = await provider.researchQuestion({ + question: "What is the market cap of Solana?", + }); + + expect(response).toContain( + "Unexpected error: Received invalid response format from Messari API", + ); }); it("should handle fetch error", async () => { @@ -117,8 +173,20 @@ describe("MessariActionProvider", () => { question: "What is the market cap of Solana?", }); - expect(response).toContain("Error querying Messari AI"); - expect(response).toContain(error.message); + expect(response).toContain("Unexpected error: Network error"); + }); + + it("should handle string error with JSON content", async () => { + // This simulates a case where an error might be stringified JSON + const stringifiedError = JSON.stringify(MOCK_ERROR_RESPONSE); + jest.spyOn(global, "fetch").mockRejectedValue(stringifiedError); + + const response = await provider.researchQuestion({ + question: "What is the market cap of Solana?", + }); + + // Should parse the JSON string and extract the error message + expect(response).toContain("Messari API Error: Internal server error"); }); }); From 16d86f4fd40a4b53ec45ea3084dd3e85554623ad Mon Sep 17 00:00:00 2001 From: bijan Date: Tue, 11 Mar 2025 23:20:58 -0400 Subject: [PATCH 08/10] update docs with rate limit --- .../src/action-providers/messari/README.md | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/typescript/agentkit/src/action-providers/messari/README.md b/typescript/agentkit/src/action-providers/messari/README.md index bafadbfa4..49948ddfe 100644 --- a/typescript/agentkit/src/action-providers/messari/README.md +++ b/typescript/agentkit/src/action-providers/messari/README.md @@ -2,9 +2,19 @@ The Messari Action Provider enables AI agents to query the [Messari AI toolkit](https://messari.io/) for crypto market research data. This provider allows agents to ask research questions about market data, statistics, rankings, historical trends, and information about specific protocols, tokens, or platforms. +## Getting an API Key + +To use the Messari Action Provider, you need to obtain a Messari API key by following these steps: + +1. Sign up for a Messari account at [messari.io](https://messari.io/) +2. After signing up, navigate to [messari.io/account/api](https://messari.io/account/api) +3. Generate your API key from the account dashboard + +Different subscription tiers provide different levels of access to the API. See the [Rate Limiting](#rate-limiting) section for details. + ## Configuration -To use the Messari Action Provider, you need to obtain a Messari API key. You can configure the provider in two ways: +Once you have your Messari API key, you can configure the provider in two ways: ### 1. Environment Variable @@ -26,6 +36,19 @@ const provider = messariActionProvider({ }); ``` +## Rate Limiting + +The Messari API has rate limits based on your subscription tier: + +| Subscription Tier | Daily Request Limit | +|-------------------|---------------------| +| Free (Unpaid) | 2 requests per day | +| Lite | 10 requests per day | +| Pro | 20 requests per day | +| Enterprise | 50 requests per day | + +If you need more than 50 requests per day, you can contact Messari's sales team to discuss a custom credit allocation system for your specific needs. + ## Actions ### `research_question` From d7c84d94b659845df41213e8bc06845cd114d63e Mon Sep 17 00:00:00 2001 From: bijan Date: Wed, 12 Mar 2025 11:14:40 -0400 Subject: [PATCH 09/10] update the prompt --- .../messari/messariActionProvider.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts index 54aa721a5..de9caf800 100644 --- a/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts +++ b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts @@ -79,21 +79,28 @@ export class MessariActionProvider extends ActionProvider { description: ` This tool will query the Messari AI toolkit with a research question about crypto markets, protocols, or tokens. -Use this tool for questions like: -1. Market data, statistics, or metrics -2. Rankings or comparisons -3. Historical data or trends -4. Information about specific protocols, tokens, or platforms -5. Financial analysis or performance data +Use this tool for questions about these specific datasets: +1. News, Podcasts, Blogs - Access aggregated crypto news, podcasts, and blogs from various sources +2. Exchanges - Information on volume, market share, and assets for major CEXs and DEXs +3. Onchain Data - Metrics on active addresses, transaction fees, and total transactions +4. Token Unlocks - Details on upcoming supply unlocks from vesting periods or mining +5. Market Data - Asset prices, trading volume, and Total Value Locked (TVL) +6. Fundraising Data - Investment information for crypto companies and projects +7. Thematic & Protocol Research - Detailed analysis of how protocols work and their mechanisms +8. Social Data - Twitter followers and Reddit subscribers metrics for cryptocurrencies, including current counts and historical changes A successful response will return the research findings from Messari. A failure response will return an error message with details. Examples of good questions: -- "What are the top 10 L2s by fees?" -- "What is the current price of ETH?" -- "What is the TVL of Arbitrum?" -- "How has Bitcoin's market cap changed over the last month?" +- News & Content: "What are the latest developments in Ethereum scaling solutions?" or "What did Vitalik Buterin say about rollups in his recent blog posts?" +- Exchanges: "Which DEXs have the highest trading volume this month?" or "How has Binance's market share changed over the past quarter?" +- Onchain Data: "What's the daily active address count for Solana?" or "Which blockchain collected the most transaction fees last week?" +- Token Unlocks: "When is the next major token unlock for Arbitrum?" or "What percentage of Optimism's supply will be unlocked in the next 3 months?" +- Market Data: "What is the current price and 24h volume for MATIC?" or "Which L1 blockchains have the highest TVL right now?" +- Fundraising: "Which crypto projects received the most funding in 2023?" or "Who were the lead investors in Celestia's latest funding round?" +- Protocol Research: "How does Morpho generate yield?" or "What is the mechanism behind Lido's liquid staking protocol?" +- Social Data: "Which cryptocurrency has gained the most Twitter followers this year?" or "How does Solana's Reddit subscriber growth compare to Cardano's?" `, schema: MessariResearchQuestionSchema, }) From ee95ec4406e95494a18ed9e09333b47059d114e0 Mon Sep 17 00:00:00 2001 From: bijan Date: Wed, 12 Mar 2025 14:57:16 -0400 Subject: [PATCH 10/10] make the description shorter --- .../src/action-providers/messari/README.md | 2 ++ .../messari/messariActionProvider.ts | 35 ++++++------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/typescript/agentkit/src/action-providers/messari/README.md b/typescript/agentkit/src/action-providers/messari/README.md index 49948ddfe..dffeb3314 100644 --- a/typescript/agentkit/src/action-providers/messari/README.md +++ b/typescript/agentkit/src/action-providers/messari/README.md @@ -10,6 +10,8 @@ To use the Messari Action Provider, you need to obtain a Messari API key by foll 2. After signing up, navigate to [messari.io/account/api](https://messari.io/account/api) 3. Generate your API key from the account dashboard +For more detailed information about authentication, refer to the [Messari API Authentication documentation](https://docs.messari.io/reference/authentication). + Different subscription tiers provide different levels of access to the API. See the [Rate Limiting](#rate-limiting) section for details. ## Configuration diff --git a/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts index de9caf800..3d956bb2a 100644 --- a/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts +++ b/typescript/agentkit/src/action-providers/messari/messariActionProvider.ts @@ -77,30 +77,17 @@ export class MessariActionProvider extends ActionProvider { @CreateAction({ name: "research_question", description: ` -This tool will query the Messari AI toolkit with a research question about crypto markets, protocols, or tokens. - -Use this tool for questions about these specific datasets: -1. News, Podcasts, Blogs - Access aggregated crypto news, podcasts, and blogs from various sources -2. Exchanges - Information on volume, market share, and assets for major CEXs and DEXs -3. Onchain Data - Metrics on active addresses, transaction fees, and total transactions -4. Token Unlocks - Details on upcoming supply unlocks from vesting periods or mining -5. Market Data - Asset prices, trading volume, and Total Value Locked (TVL) -6. Fundraising Data - Investment information for crypto companies and projects -7. Thematic & Protocol Research - Detailed analysis of how protocols work and their mechanisms -8. Social Data - Twitter followers and Reddit subscribers metrics for cryptocurrencies, including current counts and historical changes - -A successful response will return the research findings from Messari. -A failure response will return an error message with details. - -Examples of good questions: -- News & Content: "What are the latest developments in Ethereum scaling solutions?" or "What did Vitalik Buterin say about rollups in his recent blog posts?" -- Exchanges: "Which DEXs have the highest trading volume this month?" or "How has Binance's market share changed over the past quarter?" -- Onchain Data: "What's the daily active address count for Solana?" or "Which blockchain collected the most transaction fees last week?" -- Token Unlocks: "When is the next major token unlock for Arbitrum?" or "What percentage of Optimism's supply will be unlocked in the next 3 months?" -- Market Data: "What is the current price and 24h volume for MATIC?" or "Which L1 blockchains have the highest TVL right now?" -- Fundraising: "Which crypto projects received the most funding in 2023?" or "Who were the lead investors in Celestia's latest funding round?" -- Protocol Research: "How does Morpho generate yield?" or "What is the mechanism behind Lido's liquid staking protocol?" -- Social Data: "Which cryptocurrency has gained the most Twitter followers this year?" or "How does Solana's Reddit subscriber growth compare to Cardano's?" +This tool queries Messari AI for comprehensive crypto research across these datasets: +1. News/Content - Latest crypto news, blogs, podcasts +2. Exchanges - CEX/DEX volumes, market share, assets listed +3. Onchain Data - Active addresses, transaction fees, total transactions. +4. Token Unlocks - Upcoming supply unlocks, vesting schedules, and token emission details +5. Market Data - Asset prices, trading volume, market cap, TVL, and historical performance +6. Fundraising - Investment data, funding rounds, venture capital activity. +7. Protocol Research - Technical analysis of how protocols work, tokenomics, and yield mechanisms +8. Social Data - Twitter followers and Reddit subscribers metrics, growth trends + +Examples: "Which DEXs have the highest trading volume this month?", "When is Arbitrum's next major token unlock?", "How does Morpho generate yield for users?", "Which cryptocurrency has gained the most Twitter followers in 2023?", "What did Vitalik Buterin say about rollups in his recent blog posts?" `, schema: MessariResearchQuestionSchema, })