Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: farcaster client env configuration #2087

Merged
merged 22 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c562c28
add farcasterConfig and environment
sin-bufan Jan 9, 2025
088f325
refact farcaster client interface
sin-bufan Jan 9, 2025
f5bd3b3
update code about farcaster client in agent
sin-bufan Jan 9, 2025
c2e8719
refact farcaster post manager using env
sin-bufan Jan 9, 2025
dbf5fdb
add new env ENABLE_POST
sin-bufan Jan 9, 2025
6ba0dd5
update code in post and interactions using farcasterConfig
sin-bufan Jan 9, 2025
480b284
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 10, 2025
c7229b1
fix: Add thirdweb plugin import back to the agent
sin-bufan Jan 10, 2025
e808396
Merge branch 'develop' into refactor-farcaster-client
wtfsayo Jan 10, 2025
db4524f
Update packages/client-farcaster/src/post.ts
wtfsayo Jan 11, 2025
c91ca1c
Update packages/client-farcaster/src/interactions.ts
wtfsayo Jan 11, 2025
b81b9bc
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 13, 2025
38095c2
fix: improve error handling and configuration checks in Farcaster client
sin-bufan Jan 13, 2025
e322402
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 13, 2025
b773868
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 14, 2025
5347c17
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 14, 2025
124a250
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 14, 2025
9beb5c9
Merge branch 'develop' into refactor-farcaster-client
wtfsayo Jan 14, 2025
e9ad467
refactor: simplify memory structure in FarcasterInteractionManager
sin-bufan Jan 14, 2025
10b0e5c
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 15, 2025
04fbf01
Merge branch 'develop' into refactor-farcaster-client
wtfsayo Jan 15, 2025
e13f517
Merge branch 'develop' into refactor-farcaster-client
sin-bufan Jan 15, 2025
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
340 changes: 170 additions & 170 deletions .env.example

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RedisClient } from "@elizaos/adapter-redis";
import { SqliteDatabaseAdapter } from "@elizaos/adapter-sqlite";
import { AutoClientInterface } from "@elizaos/client-auto";
import { DiscordClientInterface } from "@elizaos/client-discord";
import { FarcasterAgentClient } from "@elizaos/client-farcaster";
import { FarcasterClientInterface } from "@elizaos/client-farcaster";
import { LensAgentClient } from "@elizaos/client-lens";
import { SlackClientInterface } from "@elizaos/client-slack";
import { TelegramClientInterface } from "@elizaos/client-telegram";
Expand Down Expand Up @@ -81,7 +81,6 @@ import { webSearchPlugin } from "@elizaos/plugin-web-search";

import { giphyPlugin } from "@elizaos/plugin-giphy";
import { letzAIPlugin } from "@elizaos/plugin-letzai";
import { thirdwebPlugin } from "@elizaos/plugin-thirdweb";
sin-bufan marked this conversation as resolved.
Show resolved Hide resolved

import { zksyncEraPlugin } from "@elizaos/plugin-zksync-era";

Expand Down Expand Up @@ -465,10 +464,8 @@ export async function initializeClients(
}

if (clientTypes.includes(Clients.FARCASTER)) {
// why is this one different :(
const farcasterClient = new FarcasterAgentClient(runtime);
const farcasterClient = await FarcasterClientInterface.start(runtime);
if (farcasterClient) {
farcasterClient.start();
clients.farcaster = farcasterClient;
}
}
Expand Down
4 changes: 4 additions & 0 deletions packages/client-farcaster/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { IAgentRuntime, elizaLogger } from "@elizaos/core";
import { NeynarAPIClient, isApiErrorResponse } from "@neynar/nodejs-sdk";
import { NeynarCastResponse, Cast, Profile, FidRequest, CastId } from "./types";
import { FarcasterConfig } from "./environment";

export class FarcasterClient {
runtime: IAgentRuntime;
neynar: NeynarAPIClient;
signerUuid: string;
cache: Map<string, any>;
lastInteractionTimestamp: Date;
farcasterConfig: FarcasterConfig;

constructor(opts: {
runtime: IAgentRuntime;
Expand All @@ -16,12 +18,14 @@ export class FarcasterClient {
neynar: NeynarAPIClient;
signerUuid: string;
cache: Map<string, any>;
farcasterConfig: FarcasterConfig;
}) {
this.cache = opts.cache;
this.runtime = opts.runtime;
this.neynar = opts.neynar;
this.signerUuid = opts.signerUuid;
this.lastInteractionTimestamp = new Date();
this.farcasterConfig = opts.farcasterConfig;
}

async loadCastFromNeynarResponse(neynarResponse: any): Promise<Cast> {
Expand Down
140 changes: 140 additions & 0 deletions packages/client-farcaster/src/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
parseBooleanFromText,
IAgentRuntime,
ActionTimelineType,
} from "@elizaos/core";
import { z, ZodError } from "zod";

export const DEFAULT_MAX_CAST_LENGTH = 320;
const DEFAULT_POLL_INTERVAL= 120; // 2 minutes
const DEFAULT_POST_INTERVAL_MIN = 90; // 1.5 hours
const DEFAULT_POST_INTERVAL_MAX = 180; // 3 hours
/**
* This schema defines all required/optional environment settings for Farcaster client
*/
export const farcasterEnvSchema = z.object({
FARCASTER_DRY_RUN: z.boolean(),
FARCASTER_FID: z.number().int().min(1, "Farcaster fid is required"),
MAX_CAST_LENGTH: z.number().int().default(DEFAULT_MAX_CAST_LENGTH),
FARCASTER_POLL_INTERVAL: z.number().int().default(DEFAULT_POLL_INTERVAL),
ENABLE_POST: z.boolean(),
POST_INTERVAL_MIN: z.number().int(),
POST_INTERVAL_MAX: z.number().int(),
ENABLE_ACTION_PROCESSING: z.boolean(),
ACTION_INTERVAL: z.number().int(),
POST_IMMEDIATELY: z.boolean(),
MAX_ACTIONS_PROCESSING: z.number().int(),
ACTION_TIMELINE_TYPE: z
.nativeEnum(ActionTimelineType)
.default(ActionTimelineType.ForYou),
});

export type FarcasterConfig = z.infer<typeof farcasterEnvSchema>;

function safeParseInt(
value: string | undefined | null,
defaultValue: number
): number {
if (!value) return defaultValue;
const parsed = parseInt(value, 10);
return isNaN(parsed) ? defaultValue : Math.max(1, parsed);
sin-bufan marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Validates or constructs a FarcasterConfig object using zod,
* taking values from the IAgentRuntime or process.env as needed.
*/
export async function validateFarcasterConfig(
runtime: IAgentRuntime
): Promise<FarcasterConfig> {
try {
const farcasterConfig = {
FARCASTER_DRY_RUN:
parseBooleanFromText(
runtime.getSetting("FARCASTER_DRY_RUN") ||
process.env.FARCASTER_DRY_RUN ||
"false"
),

FARCASTER_FID: safeParseInt(
runtime.getSetting("FARCASTER_FID") ||
process.env.FARCASTER_FID,
0
),

MAX_CAST_LENGTH: safeParseInt(
runtime.getSetting("MAX_CAST_LENGTH") ||
process.env.MAX_CAST_LENGTH,
DEFAULT_MAX_CAST_LENGTH
),

FARCASTER_POLL_INTERVAL: safeParseInt(
runtime.getSetting("FARCASTER_POLL_INTERVAL") ||
process.env.FARCASTER_POLL_INTERVAL,
DEFAULT_POLL_INTERVAL
),

ENABLE_POST: parseBooleanFromText(
runtime.getSetting("ENABLE_POST") ||
process.env.ENABLE_POST ||
"true"
),

POST_INTERVAL_MIN: safeParseInt(
runtime.getSetting("POST_INTERVAL_MIN") ||
process.env.POST_INTERVAL_MIN,
DEFAULT_POST_INTERVAL_MIN
),

POST_INTERVAL_MAX: safeParseInt(
runtime.getSetting("POST_INTERVAL_MAX") ||
process.env.POST_INTERVAL_MAX,
DEFAULT_POST_INTERVAL_MAX
),

ENABLE_ACTION_PROCESSING:
parseBooleanFromText(
runtime.getSetting("ENABLE_ACTION_PROCESSING") ||
process.env.ENABLE_ACTION_PROCESSING ||
"false"
) ?? false,

ACTION_INTERVAL: safeParseInt(
runtime.getSetting("ACTION_INTERVAL") ||
process.env.ACTION_INTERVAL,
5 // 5 minutes
),

POST_IMMEDIATELY:
parseBooleanFromText(
runtime.getSetting("POST_IMMEDIATELY") ||
process.env.POST_IMMEDIATELY ||
"false"
) ?? false,

MAX_ACTIONS_PROCESSING: safeParseInt(
runtime.getSetting("MAX_ACTIONS_PROCESSING") ||
process.env.MAX_ACTIONS_PROCESSING,
1
),

ACTION_TIMELINE_TYPE: (
runtime.getSetting("ACTION_TIMELINE_TYPE") ||
process.env.ACTION_TIMELINE_TYPE ||
ActionTimelineType.ForYou
) as ActionTimelineType,
};

return farcasterEnvSchema.parse(farcasterConfig);
} catch (error) {
if (error instanceof ZodError) {
const errorMessages = error.errors
.map((err) => `${err.path.join(".")}: ${err.message}`)
.join("\n");
throw new Error(
`Farcaster configuration validation failed:\n${errorMessages}`
);
}
throw error;
}
}
72 changes: 49 additions & 23 deletions packages/client-farcaster/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ import { FarcasterClient } from "./client";
import { FarcasterPostManager } from "./post";
import { FarcasterInteractionManager } from "./interactions";
import { Configuration, NeynarAPIClient } from "@neynar/nodejs-sdk";
import { validateFarcasterConfig, FarcasterConfig } from "./environment";

export class FarcasterAgentClient implements Client {
/**
* A manager that orchestrates all Farcaster operations:
* - client: base operations (Neynar client, hub connection, etc.)
* - posts: autonomous posting logic
* - interactions: handling mentions, replies, likes, etc.
*/
class FarcasterManager {
client: FarcasterClient;
posts: FarcasterPostManager;
interactions: FarcasterInteractionManager;

private signerUuid: string;

constructor(
public runtime: IAgentRuntime,
client?: FarcasterClient
) {
constructor(runtime: IAgentRuntime, farcasterConfig: FarcasterConfig) {
const cache = new Map<string, any>();

this.signerUuid = runtime.getSetting("FARCASTER_NEYNAR_SIGNER_UUID")!;

const neynarConfig = new Configuration({
Expand All @@ -25,31 +27,28 @@ export class FarcasterAgentClient implements Client {

const neynarClient = new NeynarAPIClient(neynarConfig);

this.client =
client ??
new FarcasterClient({
runtime,
ssl: true,
url:
runtime.getSetting("FARCASTER_HUB_URL") ??
"hub.pinata.cloud",
neynar: neynarClient,
signerUuid: this.signerUuid,
cache,
});

elizaLogger.info("Farcaster Neynar client initialized.");
this.client = new FarcasterClient({
runtime,
ssl: true,
url: runtime.getSetting("FARCASTER_HUB_URL") ?? "hub.pinata.cloud",
neynar: neynarClient,
signerUuid: this.signerUuid,
cache,
farcasterConfig,
});

elizaLogger.success("Farcaster Neynar client initialized.");

this.posts = new FarcasterPostManager(
this.client,
this.runtime,
runtime,
this.signerUuid,
cache
);

this.interactions = new FarcasterInteractionManager(
this.client,
this.runtime,
runtime,
this.signerUuid,
cache
);
Expand All @@ -63,3 +62,30 @@ export class FarcasterAgentClient implements Client {
await Promise.all([this.posts.stop(), this.interactions.stop()]);
}
}

export const FarcasterClientInterface: Client = {
async start(runtime: IAgentRuntime) {
const farcasterConfig = await validateFarcasterConfig(runtime);

elizaLogger.log("Farcaster client started");

const manager = new FarcasterManager(runtime, farcasterConfig);

// Start all services
await manager.start();

return manager;
sin-bufan marked this conversation as resolved.
Show resolved Hide resolved
},

async stop(runtime: IAgentRuntime) {
try {
// stop it
elizaLogger.log("Stopping farcaster client", runtime.agentId);
await runtime.clients.farcaster.stop();
sin-bufan marked this conversation as resolved.
Show resolved Hide resolved
} catch (e) {
elizaLogger.error("client-farcaster interface stop error", e);
}
},
};

export default FarcasterClientInterface;
9 changes: 4 additions & 5 deletions packages/client-farcaster/src/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ export class FarcasterInteractionManager {

this.timeout = setTimeout(
handleInteractionsLoop,
Number(
this.runtime.getSetting("FARCASTER_POLL_INTERVAL") || 120
) * 1000 // Default to 2 minutes
Number(this.client.farcasterConfig.FARCASTER_POLL_INTERVAL) *
1000 // Default to 2 minutes
wtfsayo marked this conversation as resolved.
Show resolved Hide resolved
);
};

Expand All @@ -57,7 +56,7 @@ export class FarcasterInteractionManager {
}

private async handleInteractions() {
const agentFid = Number(this.runtime.getSetting("FARCASTER_FID"));
const agentFid = this.client.farcasterConfig.FARCASTER_FID;
sin-bufan marked this conversation as resolved.
Show resolved Hide resolved

const mentions = await this.client.getMentions({
fid: agentFid,
Expand Down Expand Up @@ -231,7 +230,7 @@ export class FarcasterInteractionManager {

if (!responseContent.text) return;

if (this.runtime.getSetting("FARCASTER_DRY_RUN") === "true") {
if (this.client.farcasterConfig.FARCASTER_DRY_RUN) {
elizaLogger.info(
`Dry run: would have responded to cast ${cast.hash} with ${responseContent.text}`
);
Expand Down
Loading
Loading