Skip to content

Commit

Permalink
feat: Client side metrics (#195)
Browse files Browse the repository at this point in the history
  • Loading branch information
John-peterson-coinbase committed Feb 1, 2025
1 parent 5bf3996 commit c5c5073
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 2 deletions.
1 change: 1 addition & 0 deletions cdp-agentkit-core/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
],
"dependencies": {
"@coinbase/coinbase-sdk": "^0.15.0",
"md5": "^2.3.0",
"reflect-metadata": "^0.2.2",
"twitter-api-v2": "^1.18.2",
"viem": "^2.22.16",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";
import { WalletProvider } from "../wallet_providers/wallet_provider";
import { sendAnalyticsEvent } from "../analytics";

import "reflect-metadata";

Expand Down Expand Up @@ -82,11 +83,40 @@ export type StoredActionMetadata = Map<string, ActionMetadata>;
*/
export function CreateAction(params: CreateActionDecoratorParams) {
return (target: object, propertyKey: string, descriptor: PropertyDescriptor) => {
const existingMetadata: StoredActionMetadata =
Reflect.getMetadata(ACTION_DECORATOR_KEY, target.constructor) || new Map();
const originalMethod = descriptor.value;

const { isWalletProvider } = validateActionMethodArguments(target, propertyKey);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
descriptor.value = function (...args: any[]) {
let walletMetrics: Record<string, string> = {};

if (isWalletProvider) {
walletMetrics = {
wallet_provider: args[0].getName(),
wallet_address: args[0].getAddress(),
network_id: args[0].getNetwork().networkId,
chain_id: args[0].getNetwork().chainId,
protocol_family: args[0].getNetwork().protocolFamily,
};
}

sendAnalyticsEvent({
name: "agent_action_invocation",
action: "invoke_action",
component: "agent_action",
action_name: params.name,
class_name: target.constructor.name,
method_name: propertyKey,
...walletMetrics,
});

return originalMethod.apply(this, args);
};

const existingMetadata: StoredActionMetadata =
Reflect.getMetadata(ACTION_DECORATOR_KEY, target.constructor) || new Map();

const metaData: ActionMetadata = {
name: params.name,
description: params.description,
Expand Down
1 change: 1 addition & 0 deletions cdp-agentkit-core/typescript/src/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./sendAnalyticsEvent";
77 changes: 77 additions & 0 deletions cdp-agentkit-core/typescript/src/analytics/sendAnalyticsEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import md5 from "md5";

/**
* The required data for an analytics event
*
* Accepts arbitrary additional fields
*/
type RequiredEventData = {
/**
* The event that took place, e.g. initialize_wallet_provider, agent_action_invocation
*/
action: string;
/**
* The component that the event took place in, e.g. wallet_provider, agent_action
*/
component: string;
/**
* The name of the event. This should match the name in AEC
*/
name: string;
/**
* The timestamp of the event. If not provided, the current time will be used.
*/
timestamp?: number;
} & Record<string, string | undefined>;

/**
* Sends an analytics event to the default endpoint
*
* @param event - The event data containing required action, component and name fields
* @returns Promise that resolves when the event is sent
*/
export async function sendAnalyticsEvent(event: RequiredEventData): Promise<void> {
const timestamp = event.timestamp || Date.now();

// Prepare the event with required fields
const enhancedEvent = {
event_type: event.name,
platform: "server",
event_properties: {
component_type: event.component,
platform: "server",
project_name: "agentkit",
time_start: timestamp,
...event,
},
};

const events = [enhancedEvent];
const stringifiedEventData = JSON.stringify(events);
const uploadTime = timestamp.toString();

// Calculate checksum inline
const checksum = md5(stringifiedEventData + uploadTime);

const analyticsServiceData = {
e: stringifiedEventData,
checksum,
};

const apiEndpoint = "https://cca-lite.coinbase.com";
const eventPath = "/amp";
const eventEndPoint = `${apiEndpoint}${eventPath}`;

const response = await fetch(eventEndPoint, {
method: "POST",
mode: "no-cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(analyticsServiceData),
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
import { Network } from "../network";
import { sendAnalyticsEvent } from "../analytics";

/**
* WalletProvider is the abstract base class for all wallet providers.
*
* @abstract
*/
export abstract class WalletProvider {
/**
* Initializes the wallet provider.
*/
constructor() {
// Wait for the next tick to ensure child class is initialized
Promise.resolve().then(() => {
this.trackInitialization();
});
}

/**
* Tracks the initialization of the wallet provider.
*/
private trackInitialization() {
try {
sendAnalyticsEvent({
name: "agent_initialization",
action: "initialize_wallet_provider",
component: "wallet_provider",
wallet_provider: this.getName(),
wallet_address: this.getAddress(),
network_id: this.getNetwork().networkId,
chain_id: this.getNetwork().chainId,
protocol_family: this.getNetwork().protocolFamily,
});
} catch (error) {
console.warn("Failed to track wallet provider initialization:", error);
}
}

/**
* Get the address of the wallet provider.
*
Expand Down
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c5c5073

Please sign in to comment.