@elizaos/plugin-farcaster 2.0.3-beta.5 → 2.0.3-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/actions/index.d.ts +2 -0
  2. package/dist/actions/index.d.ts.map +1 -0
  3. package/dist/auto-enable.d.ts +4 -0
  4. package/dist/auto-enable.d.ts.map +1 -0
  5. package/dist/browser/index.browser.js +16 -0
  6. package/dist/browser/index.browser.js.map +10 -0
  7. package/dist/browser/index.d.ts +2 -0
  8. package/dist/cjs/index.d.ts +2 -0
  9. package/dist/cjs/index.node.cjs +2594 -0
  10. package/dist/cjs/index.node.cjs.map +31 -0
  11. package/dist/client/FarcasterClient.d.ts +43 -0
  12. package/dist/client/FarcasterClient.d.ts.map +1 -0
  13. package/dist/client/index.d.ts +2 -0
  14. package/dist/client/index.d.ts.map +1 -0
  15. package/dist/connector-account-provider.d.ts +16 -0
  16. package/dist/connector-account-provider.d.ts.map +1 -0
  17. package/dist/generated/specs/spec-helpers.d.ts +36 -0
  18. package/dist/generated/specs/spec-helpers.d.ts.map +1 -0
  19. package/dist/generated/specs/specs.d.ts +48 -0
  20. package/dist/generated/specs/specs.d.ts.map +1 -0
  21. package/dist/index.browser.d.ts +11 -0
  22. package/dist/index.browser.d.ts.map +1 -0
  23. package/dist/index.d.ts +10 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.node.d.ts +3 -0
  26. package/dist/index.node.d.ts.map +1 -0
  27. package/dist/managers/AgentManager.d.ts +20 -0
  28. package/dist/managers/AgentManager.d.ts.map +1 -0
  29. package/dist/managers/CastManager.d.ts +25 -0
  30. package/dist/managers/CastManager.d.ts.map +1 -0
  31. package/dist/managers/EmbedManager.d.ts +40 -0
  32. package/dist/managers/EmbedManager.d.ts.map +1 -0
  33. package/dist/managers/InteractionManager.d.ts +32 -0
  34. package/dist/managers/InteractionManager.d.ts.map +1 -0
  35. package/dist/managers/InteractionProcessor.d.ts +10 -0
  36. package/dist/managers/InteractionProcessor.d.ts.map +1 -0
  37. package/dist/managers/InteractionSource.d.ts +38 -0
  38. package/dist/managers/InteractionSource.d.ts.map +1 -0
  39. package/dist/managers/index.d.ts +7 -0
  40. package/dist/managers/index.d.ts.map +1 -0
  41. package/dist/node/index.d.ts +2 -0
  42. package/dist/node/index.node.d.ts +2 -0
  43. package/dist/node/index.node.js +2546 -0
  44. package/dist/node/index.node.js.map +31 -0
  45. package/dist/providers/index.d.ts +4 -0
  46. package/dist/providers/index.d.ts.map +1 -0
  47. package/dist/providers/profileProvider.d.ts +3 -0
  48. package/dist/providers/profileProvider.d.ts.map +1 -0
  49. package/dist/routes/webhook.d.ts +3 -0
  50. package/dist/routes/webhook.d.ts.map +1 -0
  51. package/dist/services/CastService.d.ts +133 -0
  52. package/dist/services/CastService.d.ts.map +1 -0
  53. package/dist/services/FarcasterService.d.ts +32 -0
  54. package/dist/services/FarcasterService.d.ts.map +1 -0
  55. package/dist/services/MessageService.d.ts +54 -0
  56. package/dist/services/MessageService.d.ts.map +1 -0
  57. package/dist/services/index.d.ts +4 -0
  58. package/dist/services/index.d.ts.map +1 -0
  59. package/dist/types/index.d.ts +119 -0
  60. package/dist/types/index.d.ts.map +1 -0
  61. package/dist/utils/asyncqueue.d.ts +13 -0
  62. package/dist/utils/asyncqueue.d.ts.map +1 -0
  63. package/dist/utils/callbacks.d.ts +14 -0
  64. package/dist/utils/callbacks.d.ts.map +1 -0
  65. package/dist/utils/config.d.ts +14 -0
  66. package/dist/utils/config.d.ts.map +1 -0
  67. package/dist/utils/index.d.ts +33 -0
  68. package/dist/utils/index.d.ts.map +1 -0
  69. package/dist/utils/prompts.d.ts +5 -0
  70. package/dist/utils/prompts.d.ts.map +1 -0
  71. package/dist/workflow-credential-provider.d.ts +21 -0
  72. package/dist/workflow-credential-provider.d.ts.map +1 -0
  73. package/package.json +3 -3
@@ -0,0 +1,2546 @@
1
+ // index.ts
2
+ import { getConnectorAccountManager, logger } from "@elizaos/core";
3
+
4
+ // utils/config.ts
5
+ import { parseBooleanFromText } from "@elizaos/core";
6
+ import { z as z3 } from "zod";
7
+
8
+ // types/index.ts
9
+ import * as zod from "zod";
10
+ var z2 = zod.z;
11
+ var DEFAULT_MAX_CAST_LENGTH = 320;
12
+ var DEFAULT_POLL_INTERVAL = 120;
13
+ var DEFAULT_CAST_INTERVAL_MIN = 90;
14
+ var DEFAULT_CAST_INTERVAL_MAX = 180;
15
+ var DEFAULT_CAST_CACHE_TTL = 1000 * 30 * 60;
16
+ var DEFAULT_CAST_CACHE_SIZE = 9000;
17
+ var FarcasterConfigSchema = z2.object({
18
+ FARCASTER_DRY_RUN: z2.union([z2.boolean(), z2.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
19
+ FARCASTER_FID: z2.number().int().min(1, "Farcaster fid is required"),
20
+ MAX_CAST_LENGTH: z2.number().int().default(DEFAULT_MAX_CAST_LENGTH),
21
+ FARCASTER_POLL_INTERVAL: z2.number().int().default(DEFAULT_POLL_INTERVAL),
22
+ FARCASTER_MODE: z2.enum(["polling", "webhook"]).default("polling"),
23
+ ENABLE_CAST: z2.union([z2.boolean(), z2.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
24
+ CAST_INTERVAL_MIN: z2.number().int(),
25
+ CAST_INTERVAL_MAX: z2.number().int(),
26
+ ENABLE_ACTION_PROCESSING: z2.union([z2.boolean(), z2.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
27
+ ACTION_INTERVAL: z2.number().int(),
28
+ CAST_IMMEDIATELY: z2.union([z2.boolean(), z2.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
29
+ MAX_ACTIONS_PROCESSING: z2.number().int(),
30
+ FARCASTER_SIGNER_UUID: z2.string().min(1, "FARCASTER_SIGNER_UUID is not set"),
31
+ FARCASTER_NEYNAR_API_KEY: z2.string().min(1, "FARCASTER_NEYNAR_API_KEY is not set"),
32
+ FARCASTER_HUB_URL: z2.string().min(1, "FARCASTER_HUB_URL is not set")
33
+ });
34
+ var FARCASTER_SERVICE_NAME = "farcaster";
35
+ var FARCASTER_SOURCE = "farcaster";
36
+
37
+ // utils/config.ts
38
+ var DEFAULT_FARCASTER_ACCOUNT_ID = "default";
39
+ function getProcessEnv() {
40
+ if (typeof process === "undefined") {
41
+ return {};
42
+ }
43
+ return process.env;
44
+ }
45
+ var env = getProcessEnv();
46
+ function safeParseInt(value, defaultValue) {
47
+ if (!value)
48
+ return defaultValue;
49
+ const parsed = Number.parseInt(value, 10);
50
+ return Number.isNaN(parsed) ? defaultValue : Math.max(1, parsed);
51
+ }
52
+ function stringSetting(runtime, key) {
53
+ const value = runtime.getSetting(key);
54
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
55
+ }
56
+ function characterConfig(runtime) {
57
+ const settings = runtime.character.settings;
58
+ const raw = settings?.farcaster;
59
+ return raw && typeof raw === "object" ? raw : {};
60
+ }
61
+ function parseAccountsJson(runtime) {
62
+ const raw = stringSetting(runtime, "FARCASTER_ACCOUNTS");
63
+ if (!raw)
64
+ return {};
65
+ try {
66
+ const parsed = JSON.parse(raw);
67
+ if (Array.isArray(parsed)) {
68
+ return Object.fromEntries(parsed.filter((item) => Boolean(item) && typeof item === "object").map((item) => [normalizeFarcasterAccountId(item.accountId ?? item.id), item]));
69
+ }
70
+ return parsed && typeof parsed === "object" ? parsed : {};
71
+ } catch {
72
+ return {};
73
+ }
74
+ }
75
+ function allAccountConfigs(runtime) {
76
+ return {
77
+ ...characterConfig(runtime).accounts ?? {},
78
+ ...parseAccountsJson(runtime)
79
+ };
80
+ }
81
+ function accountConfig(runtime, accountId) {
82
+ const accounts = allAccountConfigs(runtime);
83
+ return accounts[accountId] ?? accounts[normalizeFarcasterAccountId(accountId)] ?? {};
84
+ }
85
+ function rawField(record, keys) {
86
+ if (!record)
87
+ return;
88
+ for (const key of keys) {
89
+ const value = record[key];
90
+ if (typeof value === "string" && value.trim())
91
+ return value.trim();
92
+ if (typeof value === "number" || typeof value === "boolean")
93
+ return String(value);
94
+ }
95
+ return;
96
+ }
97
+ function normalizeFarcasterAccountId(accountId) {
98
+ if (typeof accountId !== "string")
99
+ return DEFAULT_FARCASTER_ACCOUNT_ID;
100
+ const trimmed = accountId.trim();
101
+ return trimmed || DEFAULT_FARCASTER_ACCOUNT_ID;
102
+ }
103
+ function listFarcasterAccountIds(runtime) {
104
+ const ids = new Set;
105
+ const config = characterConfig(runtime);
106
+ if (stringSetting(runtime, "FARCASTER_FID") || config.FARCASTER_FID && config.FARCASTER_SIGNER_UUID) {
107
+ ids.add(DEFAULT_FARCASTER_ACCOUNT_ID);
108
+ }
109
+ for (const id of Object.keys(allAccountConfigs(runtime))) {
110
+ ids.add(normalizeFarcasterAccountId(id));
111
+ }
112
+ return Array.from(ids.size ? ids : new Set([DEFAULT_FARCASTER_ACCOUNT_ID])).sort((a, b) => a.localeCompare(b));
113
+ }
114
+ function resolveDefaultFarcasterAccountId(runtime) {
115
+ const requested = stringSetting(runtime, "FARCASTER_DEFAULT_ACCOUNT_ID") ?? stringSetting(runtime, "FARCASTER_ACCOUNT_ID");
116
+ if (requested)
117
+ return normalizeFarcasterAccountId(requested);
118
+ const ids = listFarcasterAccountIds(runtime);
119
+ return ids.includes(DEFAULT_FARCASTER_ACCOUNT_ID) ? DEFAULT_FARCASTER_ACCOUNT_ID : ids[0];
120
+ }
121
+ function readFarcasterAccountId(...sources) {
122
+ for (const source of sources) {
123
+ if (!source || typeof source !== "object")
124
+ continue;
125
+ const record = source;
126
+ const parameters = record.parameters && typeof record.parameters === "object" ? record.parameters : {};
127
+ const data = record.data && typeof record.data === "object" ? record.data : {};
128
+ const metadata = record.metadata && typeof record.metadata === "object" ? record.metadata : {};
129
+ const farcaster = data.farcaster && typeof data.farcaster === "object" ? data.farcaster : {};
130
+ const value = record.accountId ?? parameters.accountId ?? data.accountId ?? farcaster.accountId ?? metadata.accountId;
131
+ if (typeof value === "string" && value.trim())
132
+ return normalizeFarcasterAccountId(value);
133
+ }
134
+ return;
135
+ }
136
+ function getFarcasterFid(runtime, accountId) {
137
+ const normalizedAccountId = normalizeFarcasterAccountId(accountId ?? resolveDefaultFarcasterAccountId(runtime));
138
+ const account = accountConfig(runtime, normalizedAccountId);
139
+ const base = characterConfig(runtime);
140
+ const allowEnv = normalizedAccountId === DEFAULT_FARCASTER_ACCOUNT_ID;
141
+ const fidStr = rawField(account, ["FARCASTER_FID", "fid"]) ?? rawField(base, ["FARCASTER_FID", "fid"]) ?? (allowEnv ? stringSetting(runtime, "FARCASTER_FID") : undefined);
142
+ if (!fidStr)
143
+ return null;
144
+ const fid = Number.parseInt(fidStr, 10);
145
+ return Number.isNaN(fid) ? null : fid;
146
+ }
147
+ function hasFarcasterEnabled(runtime, accountId) {
148
+ const normalizedAccountId = normalizeFarcasterAccountId(accountId ?? resolveDefaultFarcasterAccountId(runtime));
149
+ const account = accountConfig(runtime, normalizedAccountId);
150
+ const base = characterConfig(runtime);
151
+ const allowEnv = normalizedAccountId === DEFAULT_FARCASTER_ACCOUNT_ID;
152
+ const fid = getFarcasterFid(runtime, normalizedAccountId);
153
+ const signerUuid = rawField(account, ["FARCASTER_SIGNER_UUID", "signerUuid"]) ?? rawField(base, ["FARCASTER_SIGNER_UUID", "signerUuid"]) ?? (allowEnv ? stringSetting(runtime, "FARCASTER_SIGNER_UUID") : undefined);
154
+ const apiKey = rawField(account, ["FARCASTER_NEYNAR_API_KEY", "neynarApiKey", "apiKey"]) ?? rawField(base, ["FARCASTER_NEYNAR_API_KEY", "neynarApiKey", "apiKey"]) ?? (allowEnv ? stringSetting(runtime, "FARCASTER_NEYNAR_API_KEY") : undefined);
155
+ runtime.logger.debug(`[hasFarcasterEnabled] FID: ${fid ? "Found" : "Missing"}`);
156
+ runtime.logger.debug(`[hasFarcasterEnabled] Signer UUID: ${signerUuid ? "Found" : "Missing"}`);
157
+ runtime.logger.debug(`[hasFarcasterEnabled] API Key: ${apiKey ? "Found" : "Missing"}`);
158
+ return !!(fid && signerUuid && apiKey);
159
+ }
160
+ function validateFarcasterConfig(runtime, accountId) {
161
+ const normalizedAccountId = normalizeFarcasterAccountId(accountId ?? resolveDefaultFarcasterAccountId(runtime));
162
+ const account = accountConfig(runtime, normalizedAccountId);
163
+ const base = characterConfig(runtime);
164
+ const allowEnv = normalizedAccountId === DEFAULT_FARCASTER_ACCOUNT_ID;
165
+ const field = (keys, envKey) => rawField(account, keys) ?? rawField(base, keys) ?? (allowEnv && envKey ? stringSetting(runtime, envKey) : undefined);
166
+ const fid = getFarcasterFid(runtime, normalizedAccountId);
167
+ try {
168
+ const farcasterConfig = {
169
+ FARCASTER_DRY_RUN: field(["FARCASTER_DRY_RUN", "dryRun"], "FARCASTER_DRY_RUN") || parseBooleanFromText(env.FARCASTER_DRY_RUN || "false"),
170
+ FARCASTER_FID: fid ?? undefined,
171
+ MAX_CAST_LENGTH: safeParseInt(field(["MAX_CAST_LENGTH", "maxCastLength"], "MAX_CAST_LENGTH"), DEFAULT_MAX_CAST_LENGTH),
172
+ FARCASTER_POLL_INTERVAL: safeParseInt(field(["FARCASTER_POLL_INTERVAL", "pollInterval"], "FARCASTER_POLL_INTERVAL"), DEFAULT_POLL_INTERVAL),
173
+ ENABLE_CAST: field(["ENABLE_CAST", "enableCast"], "ENABLE_CAST") || parseBooleanFromText(env.ENABLE_CAST || "true"),
174
+ CAST_INTERVAL_MIN: safeParseInt(field(["CAST_INTERVAL_MIN", "castIntervalMin"], "CAST_INTERVAL_MIN"), DEFAULT_CAST_INTERVAL_MIN),
175
+ CAST_INTERVAL_MAX: safeParseInt(field(["CAST_INTERVAL_MAX", "castIntervalMax"], "CAST_INTERVAL_MAX"), DEFAULT_CAST_INTERVAL_MAX),
176
+ ENABLE_ACTION_PROCESSING: field(["ENABLE_ACTION_PROCESSING", "enableActionProcessing"], "ENABLE_ACTION_PROCESSING") || parseBooleanFromText(env.ENABLE_ACTION_PROCESSING || "false"),
177
+ ACTION_INTERVAL: safeParseInt(field(["ACTION_INTERVAL", "actionInterval"], "ACTION_INTERVAL"), 5),
178
+ CAST_IMMEDIATELY: field(["CAST_IMMEDIATELY", "castImmediately"], "CAST_IMMEDIATELY") || parseBooleanFromText(env.CAST_IMMEDIATELY || "false"),
179
+ MAX_ACTIONS_PROCESSING: safeParseInt(field(["MAX_ACTIONS_PROCESSING", "maxActionsProcessing"], "MAX_ACTIONS_PROCESSING"), 1),
180
+ FARCASTER_SIGNER_UUID: field(["FARCASTER_SIGNER_UUID", "signerUuid"], "FARCASTER_SIGNER_UUID"),
181
+ FARCASTER_NEYNAR_API_KEY: field(["FARCASTER_NEYNAR_API_KEY", "neynarApiKey", "apiKey"], "FARCASTER_NEYNAR_API_KEY"),
182
+ FARCASTER_HUB_URL: field(["FARCASTER_HUB_URL", "hubUrl"], "FARCASTER_HUB_URL") || "hub.pinata.cloud",
183
+ FARCASTER_MODE: field(["FARCASTER_MODE", "mode"], "FARCASTER_MODE") || "polling"
184
+ };
185
+ runtime.logger.debug(`[validateFarcasterConfig] Resolved FID: ${farcasterConfig.FARCASTER_FID}`);
186
+ runtime.logger.debug(`[validateFarcasterConfig] Resolved Signer UUID: ${farcasterConfig.FARCASTER_SIGNER_UUID ? "Found" : "Missing"}`);
187
+ runtime.logger.debug(`[validateFarcasterConfig] Resolved API Key: ${farcasterConfig.FARCASTER_NEYNAR_API_KEY ? "Found" : "Missing"}`);
188
+ const config = FarcasterConfigSchema.parse(farcasterConfig);
189
+ const isDryRun = config.FARCASTER_DRY_RUN;
190
+ runtime.logger.info("Farcaster Client Configuration:");
191
+ runtime.logger.info(`- FID: ${config.FARCASTER_FID}`);
192
+ runtime.logger.info(`- Dry Run Mode: ${isDryRun ? "enabled" : "disabled"}`);
193
+ runtime.logger.info(`- Enable Cast: ${config.ENABLE_CAST ? "enabled" : "disabled"}`);
194
+ if (config.ENABLE_CAST) {
195
+ runtime.logger.info(`- Cast Interval: ${config.CAST_INTERVAL_MIN}-${config.CAST_INTERVAL_MAX} minutes`);
196
+ runtime.logger.info(`- Cast Immediately: ${config.CAST_IMMEDIATELY ? "enabled" : "disabled"}`);
197
+ }
198
+ runtime.logger.info(`- Action Processing: ${config.ENABLE_ACTION_PROCESSING ? "enabled" : "disabled"}`);
199
+ runtime.logger.info(`- Action Interval: ${config.ACTION_INTERVAL} minutes`);
200
+ if (isDryRun) {
201
+ runtime.logger.info("Farcaster client initialized in dry run mode - no actual casts should be posted");
202
+ }
203
+ return { ...config, accountId: normalizedAccountId };
204
+ } catch (error) {
205
+ if (error instanceof z3.ZodError) {
206
+ const errorMessages = error.issues.map((err) => `${err.path.join(".")}: ${err.message}`).join(`
207
+ `);
208
+ throw new Error(`Farcaster configuration validation failed:
209
+ ${errorMessages}`);
210
+ }
211
+ throw error;
212
+ }
213
+ }
214
+
215
+ // connector-account-provider.ts
216
+ var FARCASTER_PROVIDER_ID = "farcaster";
217
+ function toConnectorAccount(runtime, accountId) {
218
+ let connected = false;
219
+ let fid;
220
+ let hubUrl = "";
221
+ try {
222
+ const config = validateFarcasterConfig(runtime, accountId);
223
+ fid = config.FARCASTER_FID;
224
+ hubUrl = config.FARCASTER_HUB_URL;
225
+ connected = Boolean(fid && config.FARCASTER_SIGNER_UUID && config.FARCASTER_NEYNAR_API_KEY);
226
+ } catch {
227
+ connected = false;
228
+ }
229
+ const now = Date.now();
230
+ return {
231
+ id: accountId,
232
+ provider: FARCASTER_PROVIDER_ID,
233
+ label: fid ? `FID ${fid}` : accountId,
234
+ role: "OWNER",
235
+ purpose: ["posting", "reading"],
236
+ accessGate: "open",
237
+ status: connected ? "connected" : "disabled",
238
+ externalId: fid ? String(fid) : undefined,
239
+ displayHandle: fid ? String(fid) : undefined,
240
+ createdAt: now,
241
+ updatedAt: now,
242
+ metadata: {
243
+ hubUrl,
244
+ fid
245
+ }
246
+ };
247
+ }
248
+ function createFarcasterConnectorAccountProvider(runtime) {
249
+ return {
250
+ provider: FARCASTER_PROVIDER_ID,
251
+ label: "Farcaster",
252
+ listAccounts: async (_manager) => {
253
+ const ids = listFarcasterAccountIds(runtime);
254
+ return ids.map((id) => toConnectorAccount(runtime, id));
255
+ },
256
+ createAccount: async (input, _manager) => {
257
+ return {
258
+ ...input,
259
+ provider: FARCASTER_PROVIDER_ID,
260
+ role: input.role ?? "OWNER",
261
+ purpose: input.purpose ?? ["posting", "reading"],
262
+ accessGate: input.accessGate ?? "open",
263
+ status: input.status ?? "pending"
264
+ };
265
+ },
266
+ patchAccount: async (_accountId, patch, _manager) => {
267
+ return { ...patch, provider: FARCASTER_PROVIDER_ID };
268
+ },
269
+ deleteAccount: async (_accountId, _manager) => {}
270
+ };
271
+ }
272
+
273
+ // generated/specs/specs.ts
274
+ var coreActionsSpec = {
275
+ version: "1.0.0",
276
+ actions: []
277
+ };
278
+ var allActionsSpec = {
279
+ version: "1.0.0",
280
+ actions: []
281
+ };
282
+ var coreProvidersSpec = {
283
+ version: "1.0.0",
284
+ providers: [
285
+ {
286
+ name: "farcasterProfile",
287
+ description: "Provides information about the agent",
288
+ dynamic: true
289
+ }
290
+ ]
291
+ };
292
+ var allProvidersSpec = {
293
+ version: "1.0.0",
294
+ providers: [
295
+ {
296
+ name: "farcasterProfile",
297
+ description: "Provides information about the agent",
298
+ dynamic: true
299
+ }
300
+ ]
301
+ };
302
+ var coreActionDocs = coreActionsSpec.actions;
303
+ var allActionDocs = allActionsSpec.actions;
304
+ var coreProviderDocs = coreProvidersSpec.providers;
305
+ var allProviderDocs = allProvidersSpec.providers;
306
+
307
+ // generated/specs/spec-helpers.ts
308
+ var coreActionMap = new Map(coreActionDocs.map((doc) => [doc.name, doc]));
309
+ var allActionMap = new Map(allActionDocs.map((doc) => [doc.name, doc]));
310
+ var coreProviderMap = new Map(coreProviderDocs.map((doc) => [doc.name, doc]));
311
+ var allProviderMap = new Map(allProviderDocs.map((doc) => [doc.name, doc]));
312
+ function getProviderSpec(name) {
313
+ return coreProviderMap.get(name) ?? allProviderMap.get(name);
314
+ }
315
+ function requireProviderSpec(name) {
316
+ const spec = getProviderSpec(name);
317
+ if (!spec) {
318
+ throw new Error(`Provider spec not found: ${name}`);
319
+ }
320
+ return spec;
321
+ }
322
+
323
+ // providers/profileProvider.ts
324
+ var spec = requireProviderSpec("farcasterProfile");
325
+ var MAX_PROFILE_FIELD_LENGTH = 280;
326
+ function truncateProfileField(value) {
327
+ return value ? value.slice(0, MAX_PROFILE_FIELD_LENGTH) : value;
328
+ }
329
+ var farcasterProfileProvider = {
330
+ name: spec.name,
331
+ description: "Provides information about the agent's Farcaster profile",
332
+ descriptionCompressed: "provide information agent Farcaster profile",
333
+ dynamic: true,
334
+ contexts: ["social_posting", "messaging", "connectors"],
335
+ contextGate: { anyOf: ["social_posting", "messaging", "connectors"] },
336
+ cacheStable: false,
337
+ cacheScope: "turn",
338
+ get: async (runtime, message, state) => {
339
+ try {
340
+ const service = runtime.getService(FARCASTER_SERVICE_NAME);
341
+ const accountId = readFarcasterAccountId(message, state);
342
+ const manager = service.getManagerForAccount(accountId, runtime.agentId) ?? service.getManagerForAccount(undefined, runtime.agentId);
343
+ if (!manager) {
344
+ runtime.logger.debug("[FarcasterProfileProvider] No managers available");
345
+ return {
346
+ text: "Farcaster profile not available.",
347
+ data: { available: false }
348
+ };
349
+ }
350
+ const selectedAccountId = manager.config.accountId;
351
+ const fid = getFarcasterFid(runtime, selectedAccountId);
352
+ if (!fid) {
353
+ runtime.logger.warn("[FarcasterProfileProvider] Invalid or missing FARCASTER_FID");
354
+ return {
355
+ text: "Invalid Farcaster FID configured.",
356
+ data: { available: false, error: "Invalid FID" }
357
+ };
358
+ }
359
+ try {
360
+ const profile = await manager.client.getProfile(fid);
361
+ const username = truncateProfileField(profile.username) ?? "";
362
+ const name = truncateProfileField(profile.name);
363
+ return {
364
+ text: `Your Farcaster profile: @${username} (FID: ${profile.fid}). ${name ? `Display name: ${name}` : ""}`,
365
+ data: {
366
+ available: true,
367
+ fid: profile.fid,
368
+ username,
369
+ name,
370
+ pfp: profile.pfp,
371
+ accountId: selectedAccountId
372
+ },
373
+ values: {
374
+ fid: profile.fid,
375
+ username
376
+ }
377
+ };
378
+ } catch (error) {
379
+ runtime.logger.error("[FarcasterProfileProvider] Error fetching profile:", typeof error === "string" ? error : error.message);
380
+ return {
381
+ text: "Unable to fetch Farcaster profile at this time.",
382
+ data: { available: false, error: "Fetch failed" }
383
+ };
384
+ }
385
+ } catch (error) {
386
+ runtime.logger.error("[FarcasterProfileProvider] Error:", typeof error === "string" ? error : error.message);
387
+ return {
388
+ text: "Farcaster service is not available.",
389
+ data: { available: false }
390
+ };
391
+ }
392
+ }
393
+ };
394
+
395
+ // providers/index.ts
396
+ var farcasterProviders = [farcasterProfileProvider];
397
+
398
+ // routes/webhook.ts
399
+ function isRecord(value) {
400
+ return typeof value === "object" && value !== null;
401
+ }
402
+ function isPositiveSafeInteger(value) {
403
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
404
+ }
405
+ function isNonEmptyString(value) {
406
+ return typeof value === "string" && value.trim().length > 0;
407
+ }
408
+ function isNeynarWebhookData(value) {
409
+ if (!isRecord(value) || !isNonEmptyString(value.type))
410
+ return false;
411
+ if (value.data === undefined)
412
+ return true;
413
+ if (!isRecord(value.data))
414
+ return false;
415
+ if (!isNonEmptyString(value.data.hash))
416
+ return false;
417
+ if (!isRecord(value.data.author) || !isPositiveSafeInteger(value.data.author.fid))
418
+ return false;
419
+ if (value.data.text !== undefined && typeof value.data.text !== "string") {
420
+ return false;
421
+ }
422
+ if (value.data.mentioned_profiles !== undefined && !(Array.isArray(value.data.mentioned_profiles) && value.data.mentioned_profiles.every((profile) => isRecord(profile) && isPositiveSafeInteger(profile.fid)))) {
423
+ return false;
424
+ }
425
+ if (value.data.parent_hash !== undefined && !isNonEmptyString(value.data.parent_hash)) {
426
+ return false;
427
+ }
428
+ if (value.data.parent_author !== undefined && !(isRecord(value.data.parent_author) && isPositiveSafeInteger(value.data.parent_author.fid))) {
429
+ return false;
430
+ }
431
+ return true;
432
+ }
433
+ var farcasterWebhookRoutes = [
434
+ {
435
+ type: "POST",
436
+ name: "Farcaster Webhook Handler",
437
+ path: "/webhook",
438
+ handler: async (req, res, runtime) => {
439
+ try {
440
+ if (!isNeynarWebhookData(req.body)) {
441
+ res.status(400).json({
442
+ success: false,
443
+ error: "Invalid webhook payload"
444
+ });
445
+ return;
446
+ }
447
+ const webhookData = req.body;
448
+ const eventType = webhookData.type;
449
+ const farcasterService = runtime.getService(FARCASTER_SERVICE_NAME);
450
+ const accountId = readFarcasterAccountId(webhookData);
451
+ if (farcasterService && accountId) {
452
+ const manager = farcasterService.getManagerForAccount?.(accountId, runtime.agentId);
453
+ if (manager?.interactions.mode === "webhook") {
454
+ await manager.interactions.processWebhookData(webhookData);
455
+ }
456
+ } else if (farcasterService) {
457
+ const managers = farcasterService.getManagersForAgent?.(runtime.agentId) ?? new Map;
458
+ await Promise.all(Array.from(managers.values()).filter((manager) => manager.interactions.mode === "webhook").map((manager) => manager.interactions.processWebhookData(webhookData)));
459
+ }
460
+ res.status(200).json({
461
+ success: true,
462
+ message: "Webhook processed successfully",
463
+ event_type: eventType,
464
+ timestamp: new Date().toISOString()
465
+ });
466
+ } catch (error) {
467
+ if (runtime.logger) {
468
+ runtime.logger.error(error instanceof Error ? error : new Error(String(error)), "Webhook processing error");
469
+ }
470
+ res.status(500).json({
471
+ success: false,
472
+ error: "Internal server error"
473
+ });
474
+ }
475
+ }
476
+ }
477
+ ];
478
+
479
+ // services/FarcasterService.ts
480
+ import {
481
+ ChannelType as ChannelType3,
482
+ Service
483
+ } from "@elizaos/core";
484
+
485
+ // managers/AgentManager.ts
486
+ import { Configuration, NeynarAPIClient } from "@neynar/nodejs-sdk";
487
+
488
+ // client/FarcasterClient.ts
489
+ import { logger as elizaLogger } from "@elizaos/core";
490
+ import { isApiErrorResponse } from "@neynar/nodejs-sdk";
491
+ import { LRUCache } from "lru-cache";
492
+
493
+ // utils/index.ts
494
+ import { stringToUuid } from "@elizaos/core";
495
+ var MAX_CAST_LENGTH = 1024;
496
+ function extractCastEmbedUrls(content) {
497
+ return Array.isArray(content.attachments) ? content.attachments.map((m) => m?.url).filter((u) => typeof u === "string" && u.trim().length > 0) : [];
498
+ }
499
+ function castId({ hash, agentId }) {
500
+ return `${hash}-${agentId}`;
501
+ }
502
+ function castUuid(props) {
503
+ return stringToUuid(castId(props));
504
+ }
505
+ function splitPostContent(content, maxLength = MAX_CAST_LENGTH) {
506
+ const paragraphs = content.split(`
507
+
508
+ `).map((p) => p.trim());
509
+ const posts = [];
510
+ let currentCast = "";
511
+ for (const paragraph of paragraphs) {
512
+ if (!paragraph)
513
+ continue;
514
+ if (`${currentCast}
515
+
516
+ ${paragraph}`.trim().length <= maxLength) {
517
+ if (currentCast) {
518
+ currentCast += `
519
+
520
+ ${paragraph}`;
521
+ } else {
522
+ currentCast = paragraph;
523
+ }
524
+ } else {
525
+ if (currentCast) {
526
+ posts.push(currentCast.trim());
527
+ }
528
+ if (paragraph.length <= maxLength) {
529
+ currentCast = paragraph;
530
+ } else {
531
+ const chunks = splitParagraph(paragraph, maxLength);
532
+ posts.push(...chunks.slice(0, -1));
533
+ currentCast = chunks[chunks.length - 1];
534
+ }
535
+ }
536
+ }
537
+ if (currentCast) {
538
+ posts.push(currentCast.trim());
539
+ }
540
+ return posts;
541
+ }
542
+ function splitParagraph(paragraph, maxLength) {
543
+ const sentences = paragraph.match(/[^.!?]+[.!?]+|[^.!?]+$/g) || [paragraph];
544
+ const chunks = [];
545
+ let currentChunk = "";
546
+ for (const sentence of sentences) {
547
+ if (`${currentChunk} ${sentence}`.trim().length <= maxLength) {
548
+ if (currentChunk) {
549
+ currentChunk += ` ${sentence}`;
550
+ } else {
551
+ currentChunk = sentence;
552
+ }
553
+ } else {
554
+ if (currentChunk) {
555
+ chunks.push(currentChunk.trim());
556
+ }
557
+ if (sentence.length <= maxLength) {
558
+ currentChunk = sentence;
559
+ } else {
560
+ const words = sentence.split(" ");
561
+ currentChunk = "";
562
+ for (const word of words) {
563
+ if (`${currentChunk} ${word}`.trim().length <= maxLength) {
564
+ if (currentChunk) {
565
+ currentChunk += ` ${word}`;
566
+ } else {
567
+ currentChunk = word;
568
+ }
569
+ } else {
570
+ if (currentChunk) {
571
+ chunks.push(currentChunk.trim());
572
+ }
573
+ currentChunk = word;
574
+ }
575
+ }
576
+ }
577
+ }
578
+ }
579
+ if (currentChunk) {
580
+ chunks.push(currentChunk.trim());
581
+ }
582
+ return chunks;
583
+ }
584
+ function lastCastCacheKey(fid) {
585
+ return `farcaster/${fid}/lastCast`;
586
+ }
587
+ function neynarCastToCast(neynarCast) {
588
+ return {
589
+ hash: neynarCast.hash,
590
+ authorFid: neynarCast.author.fid,
591
+ text: neynarCast.text,
592
+ threadId: neynarCast.thread_hash ?? undefined,
593
+ profile: {
594
+ fid: neynarCast.author.fid,
595
+ name: neynarCast.author.display_name || "anon",
596
+ username: neynarCast.author.username
597
+ },
598
+ ...neynarCast.parent_hash && neynarCast.parent_author?.fid ? {
599
+ inReplyTo: {
600
+ hash: neynarCast.parent_hash,
601
+ fid: neynarCast.parent_author.fid
602
+ }
603
+ } : {},
604
+ timestamp: new Date(neynarCast.timestamp),
605
+ embeds: neynarCast.embeds && neynarCast.embeds.length > 0 ? neynarCast.embeds : undefined
606
+ };
607
+ }
608
+ function createCastMemory({
609
+ roomId,
610
+ senderId,
611
+ runtime,
612
+ cast,
613
+ accountId
614
+ }) {
615
+ const inReplyTo = cast.inReplyTo ? castUuid({
616
+ hash: cast.inReplyTo.hash,
617
+ agentId: runtime.agentId
618
+ }) : undefined;
619
+ return {
620
+ id: castUuid({
621
+ hash: cast.hash,
622
+ agentId: runtime.agentId
623
+ }),
624
+ agentId: runtime.agentId,
625
+ entityId: senderId,
626
+ content: {
627
+ text: cast.text,
628
+ source: FARCASTER_SOURCE,
629
+ ...accountId ? { accountId } : {},
630
+ url: "",
631
+ inReplyTo,
632
+ hash: cast.hash,
633
+ threadId: cast.threadId,
634
+ attachments: cast.media && cast.media.length > 0 ? cast.media : undefined
635
+ },
636
+ metadata: {
637
+ ...accountId ? { accountId } : {}
638
+ },
639
+ roomId
640
+ };
641
+ }
642
+
643
+ // client/FarcasterClient.ts
644
+ async function logNeynarCall(op, context, fn) {
645
+ const startedAt = Date.now();
646
+ elizaLogger.debug({ sdk: "neynar", op, ...context }, `[FarcasterClient] ${op} started`);
647
+ try {
648
+ const result = await fn();
649
+ elizaLogger.info({
650
+ sdk: "neynar",
651
+ op,
652
+ ...context,
653
+ durationMs: Date.now() - startedAt
654
+ }, `[FarcasterClient] ${op} ok`);
655
+ return result;
656
+ } catch (error) {
657
+ const message = error instanceof Error ? error.message : String(error);
658
+ elizaLogger.warn({
659
+ sdk: "neynar",
660
+ op,
661
+ ...context,
662
+ durationMs: Date.now() - startedAt,
663
+ error: message
664
+ }, `[FarcasterClient] ${op} failed`);
665
+ throw error;
666
+ }
667
+ }
668
+ var castCache = new LRUCache({
669
+ max: DEFAULT_CAST_CACHE_SIZE,
670
+ ttl: DEFAULT_CAST_CACHE_TTL
671
+ });
672
+ var profileCache = new LRUCache({
673
+ max: 1000,
674
+ ttl: 1000 * 60 * 15
675
+ });
676
+
677
+ class FarcasterClient {
678
+ neynar;
679
+ signerUuid;
680
+ constructor(opts) {
681
+ this.neynar = opts.neynar;
682
+ this.signerUuid = opts.signerUuid;
683
+ }
684
+ async sendCast({
685
+ content,
686
+ inReplyTo,
687
+ embeds
688
+ }) {
689
+ const text = (content.text ?? "").trim();
690
+ const mediaEmbeds = (embeds ?? []).filter((url) => typeof url === "string" && url.trim().length > 0);
691
+ if (text.length === 0 && mediaEmbeds.length === 0) {
692
+ return [];
693
+ }
694
+ const chunks = text.length > 0 ? splitPostContent(text) : [""];
695
+ const sent = [];
696
+ for (let i = 0;i < chunks.length; i++) {
697
+ const result = await this.publishCast(chunks[i], inReplyTo, i === 0 ? mediaEmbeds : undefined);
698
+ sent.push(result);
699
+ }
700
+ return sent;
701
+ }
702
+ async publishCast(cast, parentCastId, embeds) {
703
+ try {
704
+ const result = await logNeynarCall("publishCast", {
705
+ textLen: cast.length,
706
+ parentHash: parentCastId?.hash ?? null,
707
+ embedCount: embeds?.length ?? 0
708
+ }, async () => this.neynar.publishCast({
709
+ signerUuid: this.signerUuid,
710
+ text: cast,
711
+ parent: parentCastId?.hash,
712
+ ...embeds && embeds.length > 0 ? {
713
+ embeds: embeds.map((url) => ({ url }))
714
+ } : {}
715
+ }));
716
+ if (result.success) {
717
+ return this.getCast(result.cast.hash);
718
+ }
719
+ throw new Error(`[Farcaster] Error publishing [${cast}] parentCastId: [${parentCastId}]`);
720
+ } catch (err) {
721
+ if (isApiErrorResponse(err)) {
722
+ elizaLogger.error(`Neynar error: ${JSON.stringify(err.response.data)}`);
723
+ throw err.response.data;
724
+ } else {
725
+ throw err;
726
+ }
727
+ }
728
+ }
729
+ async getCast(castHash) {
730
+ const cachedCast = castCache.get(castHash);
731
+ if (cachedCast) {
732
+ return cachedCast;
733
+ }
734
+ const response = await logNeynarCall("lookupCastByHashOrUrl", { castHash }, async () => this.neynar.lookupCastByHashOrUrl({
735
+ identifier: castHash,
736
+ type: "hash"
737
+ }));
738
+ castCache.set(castHash, response.cast);
739
+ return response.cast;
740
+ }
741
+ async getMentions(request) {
742
+ const neynarMentionsResponse = await logNeynarCall("fetchAllNotifications", { fid: request.fid, pageSize: request.pageSize }, async () => this.neynar.fetchAllNotifications({
743
+ fid: request.fid,
744
+ type: ["mentions", "replies"],
745
+ limit: request.pageSize
746
+ }));
747
+ const mentions = [];
748
+ for (const notification of neynarMentionsResponse.notifications) {
749
+ const neynarCast = notification.cast;
750
+ if (neynarCast) {
751
+ mentions.push(neynarCast);
752
+ }
753
+ }
754
+ return mentions;
755
+ }
756
+ async getProfile(fid) {
757
+ if (profileCache.has(fid)) {
758
+ return profileCache.get(fid);
759
+ }
760
+ const result = await logNeynarCall("fetchBulkUsers", { fid }, async () => this.neynar.fetchBulkUsers({ fids: [fid] }));
761
+ if (result.users.length < 1) {
762
+ throw new Error("Profile fetch failed");
763
+ }
764
+ const neynarUserProfile = result.users[0];
765
+ const profile = {
766
+ fid,
767
+ name: "",
768
+ username: ""
769
+ };
770
+ profile.name = neynarUserProfile.display_name ?? "";
771
+ profile.username = neynarUserProfile.username;
772
+ const bioText = neynarUserProfile.profile.bio.text;
773
+ if (bioText != null) {
774
+ profile.bio = bioText;
775
+ }
776
+ const profileImageUrl = neynarUserProfile.pfp_url;
777
+ if (profileImageUrl != null) {
778
+ profile.pfp = profileImageUrl;
779
+ }
780
+ profileCache.set(fid, profile);
781
+ return profile;
782
+ }
783
+ async getTimeline(request) {
784
+ const timeline = [];
785
+ const response = await logNeynarCall("fetchCastsForUser", { fid: request.fid, pageSize: request.pageSize }, async () => this.neynar.fetchCastsForUser({
786
+ fid: request.fid,
787
+ limit: request.pageSize
788
+ }));
789
+ for (const cast of response.casts) {
790
+ castCache.set(cast.hash, cast);
791
+ timeline.push(neynarCastToCast(cast));
792
+ }
793
+ const nextCursor = response.next.cursor ?? undefined;
794
+ return {
795
+ timeline,
796
+ cursor: nextCursor
797
+ };
798
+ }
799
+ clearCache() {
800
+ profileCache.clear();
801
+ castCache.clear();
802
+ }
803
+ async publishReaction(params) {
804
+ try {
805
+ const result = await logNeynarCall("publishReaction", { reactionType: params.reactionType, target: params.target }, async () => this.neynar.publishReaction({
806
+ signerUuid: this.signerUuid,
807
+ reactionType: params.reactionType,
808
+ target: params.target
809
+ }));
810
+ return { success: result.success ?? false };
811
+ } catch (err) {
812
+ if (isApiErrorResponse(err)) {
813
+ elizaLogger.error(`Neynar error publishing reaction: ${JSON.stringify(err.response.data)}`);
814
+ throw err.response.data;
815
+ }
816
+ throw err;
817
+ }
818
+ }
819
+ async deleteReaction(params) {
820
+ try {
821
+ const result = await logNeynarCall("deleteReaction", { reactionType: params.reactionType, target: params.target }, async () => this.neynar.deleteReaction({
822
+ signerUuid: this.signerUuid,
823
+ reactionType: params.reactionType,
824
+ target: params.target
825
+ }));
826
+ return { success: result.success ?? false };
827
+ } catch (err) {
828
+ if (isApiErrorResponse(err)) {
829
+ elizaLogger.error(`Neynar error deleting reaction: ${JSON.stringify(err.response.data)}`);
830
+ throw err.response.data;
831
+ }
832
+ throw err;
833
+ }
834
+ }
835
+ }
836
+
837
+ // managers/CastManager.ts
838
+ import {
839
+ createUniqueUuid,
840
+ EventType,
841
+ setTrajectoryPurpose,
842
+ withStandaloneTrajectory
843
+ } from "@elizaos/core";
844
+
845
+ // utils/callbacks.ts
846
+ function standardCastHandlerCallback({
847
+ client,
848
+ runtime,
849
+ config,
850
+ roomId,
851
+ onCompletion,
852
+ onError,
853
+ inReplyTo
854
+ }) {
855
+ const callback = async (content) => {
856
+ try {
857
+ const accountId = normalizeFarcasterAccountId(config.accountId ?? DEFAULT_FARCASTER_ACCOUNT_ID);
858
+ if (config.FARCASTER_DRY_RUN) {
859
+ runtime.logger.info(`[Farcaster] Dry run: would have cast: ${content.text}`);
860
+ return [];
861
+ }
862
+ const casts = await client.sendCast({
863
+ content,
864
+ inReplyTo,
865
+ embeds: extractCastEmbedUrls(content)
866
+ });
867
+ if (casts.length === 0) {
868
+ runtime.logger.warn("[Farcaster] No casts posted");
869
+ return [];
870
+ }
871
+ const memories = [];
872
+ for (let i = 0;i < casts.length; i++) {
873
+ const cast = casts[i];
874
+ runtime.logger.success(`[Farcaster] Published cast ${cast.hash}`);
875
+ const memory = createCastMemory({
876
+ roomId,
877
+ senderId: runtime.agentId,
878
+ runtime,
879
+ cast: neynarCastToCast(cast),
880
+ accountId
881
+ });
882
+ if (i === 0) {
883
+ memory.content.actions = content.actions;
884
+ }
885
+ await runtime.createMemory(memory, "messages");
886
+ memories.push(memory);
887
+ }
888
+ if (onCompletion) {
889
+ await onCompletion(casts, memories);
890
+ }
891
+ return memories;
892
+ } catch (error) {
893
+ runtime.logger.error("[Farcaster] Error posting cast:", typeof error === "string" ? error : error.message);
894
+ if (onError) {
895
+ await onError(error);
896
+ }
897
+ return [];
898
+ }
899
+ };
900
+ return callback;
901
+ }
902
+
903
+ // managers/CastManager.ts
904
+ class FarcasterCastManager {
905
+ client;
906
+ runtime;
907
+ fid;
908
+ timeout;
909
+ config;
910
+ isRunning = false;
911
+ constructor(opts) {
912
+ this.client = opts.client;
913
+ this.runtime = opts.runtime;
914
+ this.config = opts.config;
915
+ this.fid = this.config.FARCASTER_FID;
916
+ }
917
+ getAccountId() {
918
+ return normalizeFarcasterAccountId(this.config.accountId ?? DEFAULT_FARCASTER_ACCOUNT_ID);
919
+ }
920
+ async start() {
921
+ if (this.isRunning || !this.config.ENABLE_CAST) {
922
+ return;
923
+ }
924
+ this.isRunning = true;
925
+ this.runPeriodically();
926
+ }
927
+ async stop() {
928
+ if (this.timeout)
929
+ clearTimeout(this.timeout);
930
+ this.isRunning = false;
931
+ }
932
+ calculateDelay() {
933
+ const minMinutes = this.config.CAST_INTERVAL_MIN;
934
+ const maxMinutes = this.config.CAST_INTERVAL_MAX;
935
+ const randomMinutes = Math.floor(Math.random() * (maxMinutes - minMinutes + 1)) + minMinutes;
936
+ const delay = randomMinutes * 60 * 1000;
937
+ return { delay, randomMinutes };
938
+ }
939
+ async runPeriodically() {
940
+ if (this.config.CAST_IMMEDIATELY) {
941
+ await this.generateNewCast();
942
+ }
943
+ while (this.isRunning) {
944
+ try {
945
+ const lastPost = await this.runtime.getCache(lastCastCacheKey(this.fid));
946
+ const lastPostTimestamp = lastPost?.timestamp ?? 0;
947
+ const { delay, randomMinutes } = this.calculateDelay();
948
+ if (Date.now() > lastPostTimestamp + delay) {
949
+ await this.generateNewCast();
950
+ }
951
+ this.runtime.logger.log(`Next cast scheduled in ${randomMinutes} minutes`);
952
+ await new Promise((resolve) => {
953
+ const timeoutId = setTimeout(resolve, delay);
954
+ this.timeout = timeoutId;
955
+ });
956
+ } catch (error) {
957
+ this.runtime.logger.error({ agentId: this.runtime.agentId, error }, "[Farcaster] Error in periodic cast loop:");
958
+ }
959
+ }
960
+ }
961
+ async generateNewCast() {
962
+ await withStandaloneTrajectory(this.runtime, {
963
+ source: "plugin-farcaster:auto-cast",
964
+ metadata: {
965
+ platform: FARCASTER_SOURCE,
966
+ kind: "public_post_generation",
967
+ fid: this.fid,
968
+ accountId: this.getAccountId()
969
+ }
970
+ }, async () => {
971
+ setTrajectoryPurpose("background");
972
+ this.runtime.logger.info("Generating new cast");
973
+ try {
974
+ const worldId = createUniqueUuid(this.runtime, this.fid.toString());
975
+ const roomId = createUniqueUuid(this.runtime, `${this.fid}-home`);
976
+ const callback = standardCastHandlerCallback({
977
+ client: this.client,
978
+ runtime: this.runtime,
979
+ config: this.config,
980
+ roomId,
981
+ onCompletion: async (casts, _memories) => {
982
+ const lastCast = casts[casts.length - 1];
983
+ await this.runtime.setCache(lastCastCacheKey(this.fid), {
984
+ hash: lastCast.hash,
985
+ timestamp: new Date(lastCast.timestamp).getTime()
986
+ });
987
+ }
988
+ });
989
+ await this.runtime.emitEvent(EventType.POST_GENERATED, {
990
+ runtime: this.runtime,
991
+ callback,
992
+ worldId,
993
+ userId: this.runtime.agentId,
994
+ roomId,
995
+ source: FARCASTER_SOURCE,
996
+ accountId: this.getAccountId()
997
+ });
998
+ await this.runtime.emitEvent("FARCASTER_POST_GENERATED" /* POST_GENERATED */, {
999
+ runtime: this.runtime,
1000
+ source: FARCASTER_SOURCE,
1001
+ accountId: this.getAccountId()
1002
+ });
1003
+ } catch (error) {
1004
+ this.runtime.logger.error({ error }, "[Farcaster] Error generating new cast");
1005
+ }
1006
+ });
1007
+ }
1008
+ }
1009
+
1010
+ // managers/InteractionManager.ts
1011
+ import {
1012
+ ChannelType,
1013
+ createUniqueUuid as createUniqueUuid2,
1014
+ stringToUuid as stringToUuid2
1015
+ } from "@elizaos/core";
1016
+
1017
+ // utils/asyncqueue.ts
1018
+ class AsyncQueue {
1019
+ queue = [];
1020
+ running = 0;
1021
+ emptyListeners = [];
1022
+ maxConcurrent;
1023
+ constructor(maxConcurrent = 1) {
1024
+ this.maxConcurrent = maxConcurrent;
1025
+ }
1026
+ async submit(work) {
1027
+ return new Promise((resolve, reject) => {
1028
+ this.queue.push(async () => {
1029
+ try {
1030
+ resolve(await work());
1031
+ } catch (err) {
1032
+ reject(err);
1033
+ }
1034
+ });
1035
+ this.doNextWork();
1036
+ });
1037
+ }
1038
+ async doNextWork() {
1039
+ if (this.running >= this.maxConcurrent) {
1040
+ return;
1041
+ }
1042
+ const work = this.queue.shift();
1043
+ if (!work) {
1044
+ this.checkIfEmptyAndNotify();
1045
+ return;
1046
+ }
1047
+ this.running++;
1048
+ try {
1049
+ await work();
1050
+ } catch {} finally {
1051
+ this.running--;
1052
+ this.doNextWork();
1053
+ }
1054
+ }
1055
+ async size() {
1056
+ return this.queue.length;
1057
+ }
1058
+ async waitUntilFinished() {
1059
+ return new Promise((resolve) => {
1060
+ if (this.queue.length === 0 && this.running === 0) {
1061
+ resolve();
1062
+ } else {
1063
+ this.emptyListeners.push(resolve);
1064
+ }
1065
+ });
1066
+ }
1067
+ checkIfEmptyAndNotify() {
1068
+ if (this.queue.length === 0 && this.running === 0) {
1069
+ while (this.emptyListeners.length) {
1070
+ const listener = this.emptyListeners.shift();
1071
+ if (listener)
1072
+ listener();
1073
+ }
1074
+ }
1075
+ }
1076
+ }
1077
+
1078
+ // managers/EmbedManager.ts
1079
+ import { ModelType, withStandaloneTrajectory as withStandaloneTrajectory2 } from "@elizaos/core";
1080
+ function isEmbedUrl(embed) {
1081
+ return "url" in embed;
1082
+ }
1083
+ function isEmbedCast(embed) {
1084
+ return "cast" in embed;
1085
+ }
1086
+ function getMediaTypeFromUrl(url, contentType) {
1087
+ const lowerUrl = url.toLowerCase();
1088
+ const lowerContentType = contentType?.toLowerCase() || "";
1089
+ if (lowerContentType.startsWith("image/"))
1090
+ return "image";
1091
+ if (lowerContentType.startsWith("video/"))
1092
+ return "video";
1093
+ if (lowerContentType.startsWith("audio/"))
1094
+ return "audio";
1095
+ const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp", ".ico"];
1096
+ const videoExtensions = [".mp4", ".webm", ".mov", ".avi", ".mkv", ".m4v"];
1097
+ const audioExtensions = [".mp3", ".wav", ".ogg", ".flac", ".aac", ".m4a"];
1098
+ if (imageExtensions.some((ext) => lowerUrl.includes(ext)))
1099
+ return "image";
1100
+ if (videoExtensions.some((ext) => lowerUrl.includes(ext)))
1101
+ return "video";
1102
+ if (audioExtensions.some((ext) => lowerUrl.includes(ext)))
1103
+ return "audio";
1104
+ return "webpage";
1105
+ }
1106
+
1107
+ class EmbedManager {
1108
+ runtime;
1109
+ embedCache = new Map;
1110
+ constructor(runtime) {
1111
+ this.runtime = runtime;
1112
+ }
1113
+ async processEmbeds(embeds) {
1114
+ if (embeds.length === 0) {
1115
+ return [];
1116
+ }
1117
+ this.runtime.logger.info({ embedCount: embeds.length }, "[EmbedManager] Processing embeds from cast");
1118
+ const processedMedia = [];
1119
+ for (const embed of embeds) {
1120
+ try {
1121
+ const processed = await this.processEmbed(embed);
1122
+ if (processed) {
1123
+ processedMedia.push(this.toMedia(processed));
1124
+ }
1125
+ } catch (error) {
1126
+ this.runtime.logger.warn({ error: error instanceof Error ? error.message : String(error) }, "[EmbedManager] Failed to process embed");
1127
+ }
1128
+ }
1129
+ this.runtime.logger.info({
1130
+ processedCount: processedMedia.length,
1131
+ types: processedMedia.map((m) => m.source)
1132
+ }, "[EmbedManager] Finished processing embeds");
1133
+ return processedMedia;
1134
+ }
1135
+ async processEmbed(embed) {
1136
+ if (isEmbedUrl(embed)) {
1137
+ return this.processUrlEmbed(embed);
1138
+ } else if (isEmbedCast(embed)) {
1139
+ return this.processCastEmbed(embed);
1140
+ }
1141
+ this.runtime.logger.debug("[EmbedManager] Unknown embed type");
1142
+ return null;
1143
+ }
1144
+ async processUrlEmbed(embed) {
1145
+ const { url, metadata } = embed;
1146
+ const embedId = `embed-${this.hashUrl(url)}`;
1147
+ const cached = this.embedCache.get(embedId);
1148
+ if (cached) {
1149
+ return cached;
1150
+ }
1151
+ const contentType = metadata?.content_type;
1152
+ const mediaType = getMediaTypeFromUrl(url, contentType);
1153
+ if (metadata?.frame) {
1154
+ return this.processFrameEmbed(embed, embedId);
1155
+ }
1156
+ let processed;
1157
+ switch (mediaType) {
1158
+ case "image":
1159
+ processed = await this.processImageEmbed(embed, embedId);
1160
+ break;
1161
+ case "video":
1162
+ processed = await this.processVideoEmbed(embed, embedId);
1163
+ break;
1164
+ case "audio":
1165
+ processed = await this.processAudioEmbed(embed, embedId);
1166
+ break;
1167
+ default:
1168
+ processed = await this.processWebpageEmbed(embed, embedId);
1169
+ }
1170
+ this.embedCache.set(embedId, processed);
1171
+ return processed;
1172
+ }
1173
+ async processImageEmbed(embed, embedId) {
1174
+ const { url, metadata } = embed;
1175
+ let description = "An image attachment";
1176
+ let title = "Image";
1177
+ try {
1178
+ const result = await withStandaloneTrajectory2(this.runtime, { source: "farcaster-embed" }, async () => this.runtime.useModel(ModelType.IMAGE_DESCRIPTION, {
1179
+ prompt: "Analyze this image and provide a concise title and description. Focus on the main subject and any notable details.",
1180
+ imageUrl: url
1181
+ }));
1182
+ if (result && typeof result === "object") {
1183
+ const typedResult = result;
1184
+ description = typedResult.description || description;
1185
+ title = typedResult.title || title;
1186
+ } else if (typeof result === "string") {
1187
+ description = result;
1188
+ }
1189
+ this.runtime.logger.info({
1190
+ url: `${url.substring(0, 60)}...`,
1191
+ descriptionLength: description.length,
1192
+ title
1193
+ }, "[EmbedManager] Processed image with vision model");
1194
+ } catch (error) {
1195
+ this.runtime.logger.warn({ url, error: error instanceof Error ? error.message : String(error) }, "[EmbedManager] Failed to describe image, using fallback");
1196
+ }
1197
+ return {
1198
+ id: embedId,
1199
+ url,
1200
+ type: "image",
1201
+ title,
1202
+ description,
1203
+ text: description,
1204
+ source: "Farcaster",
1205
+ metadata: {
1206
+ width: metadata?.image?.width_px,
1207
+ height: metadata?.image?.height_px,
1208
+ contentType: metadata?.content_type || "image/*"
1209
+ }
1210
+ };
1211
+ }
1212
+ async processVideoEmbed(embed, embedId) {
1213
+ const { url, metadata } = embed;
1214
+ const description = metadata?.video?.duration_s ? `Video (${Math.round(metadata.video.duration_s)}s)` : "Video attachment";
1215
+ return {
1216
+ id: embedId,
1217
+ url,
1218
+ type: "video",
1219
+ title: "Video",
1220
+ description,
1221
+ text: description,
1222
+ source: "Farcaster",
1223
+ metadata: {
1224
+ duration: metadata?.video?.duration_s,
1225
+ contentType: metadata?.content_type || "video/*"
1226
+ }
1227
+ };
1228
+ }
1229
+ async processAudioEmbed(embed, embedId) {
1230
+ const { url, metadata } = embed;
1231
+ const description = "Audio attachment";
1232
+ return {
1233
+ id: embedId,
1234
+ url,
1235
+ type: "audio",
1236
+ title: "Audio",
1237
+ description,
1238
+ text: description,
1239
+ source: "Farcaster",
1240
+ metadata: {
1241
+ contentType: metadata?.content_type || "audio/*"
1242
+ }
1243
+ };
1244
+ }
1245
+ async processWebpageEmbed(embed, embedId) {
1246
+ const { url, metadata } = embed;
1247
+ const html = metadata?.html;
1248
+ const title = html?.ogTitle || html?.ogSiteName || "Web Page";
1249
+ const hostnameMatch = url.match(/^(?:https?:\/\/)?([^/?#]+)/);
1250
+ const hostname = hostnameMatch ? hostnameMatch[1] : url;
1251
+ const description = html?.ogDescription || `Link to ${hostname}`;
1252
+ return {
1253
+ id: embedId,
1254
+ url,
1255
+ type: "webpage",
1256
+ title,
1257
+ description,
1258
+ text: `${title}: ${description}`,
1259
+ source: "Web",
1260
+ metadata: {
1261
+ contentType: "text/html"
1262
+ }
1263
+ };
1264
+ }
1265
+ async processFrameEmbed(embed, embedId) {
1266
+ const { url, metadata } = embed;
1267
+ const frame = metadata?.frame;
1268
+ const title = frame?.title || "Farcaster Frame";
1269
+ const description = `Interactive Frame: ${title}`;
1270
+ return {
1271
+ id: embedId,
1272
+ url,
1273
+ type: "frame",
1274
+ title,
1275
+ description,
1276
+ text: description,
1277
+ source: "Frame",
1278
+ metadata: {
1279
+ contentType: "application/x-farcaster-frame"
1280
+ }
1281
+ };
1282
+ }
1283
+ async processCastEmbed(embed) {
1284
+ const cast = embed.cast;
1285
+ const embedId = `cast-${cast.hash}`;
1286
+ const cached = this.embedCache.get(embedId);
1287
+ if (cached) {
1288
+ return cached;
1289
+ }
1290
+ const authorUsername = cast.author.username || "unknown";
1291
+ const title = `Quoted cast from @${authorUsername}`;
1292
+ const description = cast.text || "";
1293
+ const processed = {
1294
+ id: embedId,
1295
+ url: `https://warpcast.com/${authorUsername}/${cast.hash.slice(0, 10)}`,
1296
+ type: "cast",
1297
+ title,
1298
+ description,
1299
+ text: `[Quote from @${authorUsername}]: ${description}`,
1300
+ source: "Farcaster",
1301
+ metadata: {
1302
+ castHash: cast.hash,
1303
+ authorFid: cast.author.fid,
1304
+ authorUsername
1305
+ }
1306
+ };
1307
+ this.embedCache.set(embedId, processed);
1308
+ return processed;
1309
+ }
1310
+ toMedia(embed) {
1311
+ return {
1312
+ id: embed.id,
1313
+ url: embed.url,
1314
+ title: embed.title || embed.type,
1315
+ source: embed.source,
1316
+ description: embed.description,
1317
+ text: embed.text
1318
+ };
1319
+ }
1320
+ hashUrl(url) {
1321
+ let hash = 0;
1322
+ for (let i = 0;i < url.length; i++) {
1323
+ const char = url.charCodeAt(i);
1324
+ hash = (hash << 5) - hash + char;
1325
+ hash = hash & hash;
1326
+ }
1327
+ return Math.abs(hash).toString(36);
1328
+ }
1329
+ clearCache() {
1330
+ this.embedCache.clear();
1331
+ }
1332
+ }
1333
+
1334
+ // managers/InteractionSource.ts
1335
+ class FarcasterInteractionSource {
1336
+ client;
1337
+ runtime;
1338
+ config;
1339
+ processor;
1340
+ isRunning = false;
1341
+ constructor(params) {
1342
+ this.client = params.client;
1343
+ this.runtime = params.runtime;
1344
+ this.config = params.config;
1345
+ this.processor = params.processor;
1346
+ }
1347
+ }
1348
+
1349
+ class FarcasterPollingSource extends FarcasterInteractionSource {
1350
+ timeout;
1351
+ async start() {
1352
+ this.runtime.logger.info("Starting Farcaster polling mode");
1353
+ if (this.isRunning) {
1354
+ return;
1355
+ }
1356
+ this.isRunning = true;
1357
+ this.runPeriodically();
1358
+ }
1359
+ async stop() {
1360
+ this.runtime.logger.info("Stopping Farcaster polling mode");
1361
+ if (this.timeout)
1362
+ clearTimeout(this.timeout);
1363
+ this.isRunning = false;
1364
+ }
1365
+ async runPeriodically() {
1366
+ while (this.isRunning) {
1367
+ try {
1368
+ await this.pollForInteractions();
1369
+ const delay = this.config.FARCASTER_POLL_INTERVAL * 1000;
1370
+ await new Promise((resolve) => {
1371
+ this.timeout = setTimeout(resolve, delay);
1372
+ });
1373
+ } catch (error) {
1374
+ this.runtime.logger.error({ error }, "[Farcaster] Error in polling:");
1375
+ }
1376
+ }
1377
+ }
1378
+ async pollForInteractions() {
1379
+ const agentFid = this.config.FARCASTER_FID;
1380
+ const mentions = await this.client.getMentions({
1381
+ fid: agentFid,
1382
+ pageSize: 20
1383
+ });
1384
+ for (const cast of mentions) {
1385
+ try {
1386
+ const mention = neynarCastToCast(cast);
1387
+ const memoryId = castUuid({
1388
+ agentId: this.runtime.agentId,
1389
+ hash: mention.hash
1390
+ });
1391
+ if (await this.runtime.getMemoryById(memoryId)) {
1392
+ continue;
1393
+ }
1394
+ this.runtime.logger.info({ hash: mention.hash }, "New Cast found");
1395
+ if (mention.authorFid === agentFid) {
1396
+ const memory = await this.processor.ensureCastConnection(mention);
1397
+ await this.runtime.addEmbeddingToMemory(memory);
1398
+ await this.runtime.createMemory(memory, "messages");
1399
+ continue;
1400
+ }
1401
+ await this.processor.processMention(cast);
1402
+ } catch (error) {
1403
+ this.runtime.logger.error({ error }, "[Farcaster] Error processing mention:");
1404
+ }
1405
+ }
1406
+ }
1407
+ }
1408
+
1409
+ class FarcasterWebhookSource extends FarcasterInteractionSource {
1410
+ async start() {
1411
+ this.runtime.logger.info("Starting Farcaster webhook mode");
1412
+ if (this.isRunning) {
1413
+ return;
1414
+ }
1415
+ this.isRunning = true;
1416
+ this.runtime.logger.info("Webhook source is active - waiting for webhook events");
1417
+ }
1418
+ async stop() {
1419
+ this.runtime.logger.info("Stopping Farcaster webhook mode");
1420
+ this.isRunning = false;
1421
+ }
1422
+ async processWebhookData(webhookData) {
1423
+ if (!this.isRunning) {
1424
+ this.runtime.logger.warn("Webhook source is not running, ignoring webhook data");
1425
+ return;
1426
+ }
1427
+ try {
1428
+ await this.processor.processWebhookData(webhookData);
1429
+ } catch (error) {
1430
+ this.runtime.logger.error({ error }, "[Farcaster] Error processing webhook data:");
1431
+ }
1432
+ }
1433
+ }
1434
+ function createFarcasterInteractionSource(params) {
1435
+ const mode = params.config.FARCASTER_MODE;
1436
+ switch (mode) {
1437
+ case "webhook":
1438
+ return new FarcasterWebhookSource(params);
1439
+ default:
1440
+ return new FarcasterPollingSource(params);
1441
+ }
1442
+ }
1443
+
1444
+ // managers/InteractionManager.ts
1445
+ class FarcasterInteractionManager {
1446
+ client;
1447
+ runtime;
1448
+ config;
1449
+ asyncQueue;
1450
+ embedManager;
1451
+ mode;
1452
+ source;
1453
+ constructor(opts) {
1454
+ this.client = opts.client;
1455
+ this.runtime = opts.runtime;
1456
+ this.config = opts.config;
1457
+ this.asyncQueue = new AsyncQueue(1);
1458
+ this.embedManager = new EmbedManager(opts.runtime);
1459
+ this.mode = opts.config.FARCASTER_MODE;
1460
+ this.source = createFarcasterInteractionSource({
1461
+ client: this.client,
1462
+ runtime: this.runtime,
1463
+ config: this.config,
1464
+ processor: this
1465
+ });
1466
+ this.runtime.logger.info(`Farcaster interaction mode: ${this.mode}`);
1467
+ }
1468
+ getAccountId() {
1469
+ return normalizeFarcasterAccountId(this.config.accountId ?? DEFAULT_FARCASTER_ACCOUNT_ID);
1470
+ }
1471
+ async processMention(cast) {
1472
+ const agentFid = this.config.FARCASTER_FID;
1473
+ const agent = await this.client.getProfile(agentFid);
1474
+ const mention = neynarCastToCast(cast);
1475
+ if (mention.embeds && mention.embeds.length > 0) {
1476
+ try {
1477
+ this.runtime.logger.debug({ castHash: cast.hash, embedCount: mention.embeds.length }, "[Farcaster] Processing embeds for mention");
1478
+ const processedMedia = await this.embedManager.processEmbeds(mention.embeds);
1479
+ mention.media = processedMedia;
1480
+ this.runtime.logger.info({ castHash: cast.hash, mediaCount: processedMedia.length }, "[Farcaster] Processed embeds for mention");
1481
+ } catch (error) {
1482
+ this.runtime.logger.warn({
1483
+ error: error instanceof Error ? error.message : String(error),
1484
+ castHash: cast.hash
1485
+ }, "[Farcaster] Failed to process embeds, continuing without media");
1486
+ }
1487
+ }
1488
+ await this.handleMentionCast({ agent, mention, cast });
1489
+ }
1490
+ async processReply(cast) {
1491
+ const agentFid = this.config.FARCASTER_FID;
1492
+ const agent = await this.client.getProfile(agentFid);
1493
+ const reply = neynarCastToCast(cast);
1494
+ if (reply.embeds && reply.embeds.length > 0) {
1495
+ try {
1496
+ this.runtime.logger.debug({ castHash: cast.hash, embedCount: reply.embeds.length }, "[Farcaster] Processing embeds for reply");
1497
+ const processedMedia = await this.embedManager.processEmbeds(reply.embeds);
1498
+ reply.media = processedMedia;
1499
+ this.runtime.logger.info({ castHash: cast.hash, mediaCount: processedMedia.length }, "[Farcaster] Processed embeds for reply");
1500
+ } catch (error) {
1501
+ this.runtime.logger.warn({
1502
+ error: error instanceof Error ? error.message : String(error),
1503
+ castHash: cast.hash
1504
+ }, "[Farcaster] Failed to process embeds, continuing without media");
1505
+ }
1506
+ }
1507
+ await this.handleMentionCast({ agent, mention: reply, cast });
1508
+ }
1509
+ async processWebhookData(webhookData) {
1510
+ if (webhookData.type !== "cast.created" || !webhookData.data) {
1511
+ this.runtime.logger.debug("Ignoring non-cast webhook event:", webhookData.type);
1512
+ return;
1513
+ }
1514
+ const castData = webhookData.data;
1515
+ const agentFid = this.config.FARCASTER_FID;
1516
+ if (!castData.author || !castData.hash || typeof castData.author.fid !== "number") {
1517
+ this.runtime.logger.warn("Invalid webhook cast data structure - missing author, hash, or author.fid");
1518
+ return;
1519
+ }
1520
+ if (castData.author.fid === agentFid) {
1521
+ this.runtime.logger.debug("Skipping webhook event from agent itself");
1522
+ return;
1523
+ }
1524
+ const memoryId = castUuid({
1525
+ agentId: this.runtime.agentId,
1526
+ hash: castData.hash
1527
+ });
1528
+ if (await this.runtime.getMemoryById(memoryId)) {
1529
+ this.runtime.logger.debug("Skipping already processed webhook cast:", castData.hash);
1530
+ return;
1531
+ }
1532
+ const isMention = castData.mentioned_profiles?.some((profile) => profile.fid === agentFid);
1533
+ const isReply = castData.parent_hash && castData.parent_author?.fid === agentFid;
1534
+ if (isMention) {
1535
+ const username = castData.author.username || "unknown";
1536
+ const text = castData.text || "";
1537
+ this.runtime.logger.info(`Processing webhook MENTION from @${username}: "${text}"`);
1538
+ try {
1539
+ const neynarCast = await this.client.getCast(castData.hash);
1540
+ await this.processMention(neynarCast);
1541
+ } catch (error) {
1542
+ this.runtime.logger.error({ agentId: this.runtime.agentId, error }, `Failed to process webhook mention from @${username}`);
1543
+ }
1544
+ } else if (isReply) {
1545
+ const username = castData.author.username || "unknown";
1546
+ const text = castData.text || "";
1547
+ this.runtime.logger.info(`Processing webhook REPLY from @${username}: "${text}"`);
1548
+ try {
1549
+ const neynarCast = await this.client.getCast(castData.hash);
1550
+ await this.processReply(neynarCast);
1551
+ } catch (error) {
1552
+ this.runtime.logger.error({ error }, `Failed to process webhook reply from @${username}:`);
1553
+ }
1554
+ } else {
1555
+ this.runtime.logger.debug("Webhook cast is neither mention nor reply to agent");
1556
+ }
1557
+ }
1558
+ async ensureCastConnection(cast) {
1559
+ return await this.asyncQueue.submit(async () => {
1560
+ const memoryId = castUuid({
1561
+ agentId: this.runtime.agentId,
1562
+ hash: cast.hash
1563
+ });
1564
+ const conversationId = cast.threadId ?? cast.inReplyTo?.hash ?? cast.hash;
1565
+ const entityId = createUniqueUuid2(this.runtime, cast.authorFid.toString());
1566
+ const worldId = createUniqueUuid2(this.runtime, cast.authorFid.toString());
1567
+ const roomId = createUniqueUuid2(this.runtime, conversationId);
1568
+ if (entityId !== this.runtime.agentId) {
1569
+ await this.runtime.ensureConnection({
1570
+ entityId,
1571
+ roomId,
1572
+ worldName: `${cast.profile.username}'s Farcaster`,
1573
+ userName: cast.profile.username,
1574
+ name: cast.profile.name,
1575
+ source: FARCASTER_SOURCE,
1576
+ type: ChannelType.THREAD,
1577
+ channelId: conversationId,
1578
+ messageServerId: stringToUuid2(cast.authorFid.toString()),
1579
+ worldId,
1580
+ metadata: {
1581
+ accountId: this.getAccountId(),
1582
+ ownership: { ownerId: cast.authorFid.toString() },
1583
+ farcaster: {
1584
+ accountId: this.getAccountId(),
1585
+ username: cast.profile.username,
1586
+ id: cast.authorFid.toString(),
1587
+ name: cast.profile.name
1588
+ }
1589
+ }
1590
+ });
1591
+ }
1592
+ let text = cast.text;
1593
+ if (cast.media && cast.media.length > 0) {
1594
+ const attachmentTypes = cast.media.map((m) => m.source || "attachment").join(", ");
1595
+ text = `${cast.text}
1596
+
1597
+ (Attachments: ${cast.media.length} - ${attachmentTypes})`;
1598
+ }
1599
+ const memory = {
1600
+ id: memoryId,
1601
+ agentId: this.runtime.agentId,
1602
+ content: {
1603
+ text,
1604
+ inReplyTo: cast.inReplyTo?.hash ? castUuid({
1605
+ agentId: this.runtime.agentId,
1606
+ hash: cast.inReplyTo.hash
1607
+ }) : undefined,
1608
+ source: FARCASTER_SOURCE,
1609
+ accountId: this.getAccountId(),
1610
+ channelType: ChannelType.THREAD,
1611
+ attachments: cast.media && cast.media.length > 0 ? cast.media : undefined
1612
+ },
1613
+ entityId,
1614
+ roomId,
1615
+ createdAt: cast.timestamp.getTime(),
1616
+ metadata: {
1617
+ accountId: this.getAccountId()
1618
+ }
1619
+ };
1620
+ return memory;
1621
+ });
1622
+ }
1623
+ async buildThreadForCast(cast, skipMemoryId) {
1624
+ const thread = [];
1625
+ const visited = new Set;
1626
+ const client = this.client;
1627
+ const runtime = this.runtime;
1628
+ const self = this;
1629
+ async function processThread(currentCast) {
1630
+ const memoryId = castUuid({
1631
+ hash: currentCast.hash,
1632
+ agentId: runtime.agentId
1633
+ });
1634
+ if (visited.has(currentCast.hash) || skipMemoryId.has(memoryId)) {
1635
+ return;
1636
+ }
1637
+ visited.add(currentCast.hash);
1638
+ const memory = await runtime.getMemoryById(memoryId);
1639
+ if (!memory) {
1640
+ runtime.logger.info({ hash: currentCast.hash }, "Creating memory for cast");
1641
+ const newMemory = await self.ensureCastConnection(currentCast);
1642
+ await runtime.createMemory(newMemory, "messages");
1643
+ runtime.emitEvent("FARCASTER_THREAD_CAST_CREATED" /* THREAD_CAST_CREATED */, {
1644
+ runtime,
1645
+ memory: newMemory,
1646
+ cast: currentCast,
1647
+ source: FARCASTER_SOURCE,
1648
+ accountId: self.getAccountId()
1649
+ });
1650
+ }
1651
+ thread.unshift(currentCast);
1652
+ if (currentCast.inReplyTo) {
1653
+ const parentCast = await client.getCast(currentCast.inReplyTo.hash);
1654
+ await processThread(neynarCastToCast(parentCast));
1655
+ }
1656
+ }
1657
+ await processThread(cast);
1658
+ return thread;
1659
+ }
1660
+ async handleMentionCast({
1661
+ agent,
1662
+ mention,
1663
+ cast
1664
+ }) {
1665
+ if (mention.profile.fid === agent.fid) {
1666
+ this.runtime.logger.info({ hash: mention.hash }, "skipping cast from bot itself");
1667
+ return;
1668
+ }
1669
+ const memory = await this.ensureCastConnection(mention);
1670
+ await this.buildThreadForCast(mention, memory.id ? new Set([memory.id]) : new Set);
1671
+ if (!memory.content.text || memory.content.text.trim() === "") {
1672
+ this.runtime.logger.info({ hash: mention.hash }, "skipping cast with no text");
1673
+ return;
1674
+ }
1675
+ const callback = standardCastHandlerCallback({
1676
+ client: this.client,
1677
+ runtime: this.runtime,
1678
+ config: this.config,
1679
+ roomId: memory.roomId,
1680
+ inReplyTo: {
1681
+ hash: mention.hash,
1682
+ fid: mention.authorFid
1683
+ }
1684
+ });
1685
+ try {
1686
+ if (!this.runtime.messageService) {
1687
+ this.runtime.logger.warn("[Farcaster] messageService not available, skipping mention handling");
1688
+ return;
1689
+ }
1690
+ await this.runtime.messageService.handleMessage(this.runtime, memory, callback);
1691
+ } catch (error) {
1692
+ this.runtime.logger.error({
1693
+ error: error instanceof Error ? error.message : String(error),
1694
+ castHash: mention.hash
1695
+ }, "[Farcaster] Error processing mention");
1696
+ }
1697
+ const mentionPayload = {
1698
+ runtime: this.runtime,
1699
+ memory,
1700
+ cast,
1701
+ source: FARCASTER_SOURCE,
1702
+ accountId: this.getAccountId(),
1703
+ callback
1704
+ };
1705
+ this.runtime.emitEvent("FARCASTER_MENTION_RECEIVED" /* MENTION_RECEIVED */, mentionPayload);
1706
+ }
1707
+ async start() {
1708
+ this.runtime.logger.info(`Starting Farcaster interaction manager in ${this.mode} mode`);
1709
+ await this.source.start();
1710
+ }
1711
+ async stop() {
1712
+ this.runtime.logger.info("Stopping Farcaster interaction manager");
1713
+ await this.source.stop();
1714
+ }
1715
+ }
1716
+
1717
+ // managers/AgentManager.ts
1718
+ class FarcasterAgentManager {
1719
+ runtime;
1720
+ client;
1721
+ casts;
1722
+ interactions;
1723
+ config;
1724
+ constructor(runtime, config) {
1725
+ this.runtime = runtime;
1726
+ this.config = config;
1727
+ const signerUuid = config.FARCASTER_SIGNER_UUID;
1728
+ const neynarConfig = new Configuration({
1729
+ apiKey: config.FARCASTER_NEYNAR_API_KEY
1730
+ });
1731
+ const neynar = new NeynarAPIClient(neynarConfig);
1732
+ const client = new FarcasterClient({ neynar, signerUuid });
1733
+ this.client = client;
1734
+ runtime.logger.success("Farcaster Neynar client initialized.");
1735
+ this.interactions = new FarcasterInteractionManager({
1736
+ client,
1737
+ runtime,
1738
+ config
1739
+ });
1740
+ this.casts = new FarcasterCastManager({ client, runtime, config });
1741
+ }
1742
+ async start() {
1743
+ await Promise.all([this.casts.start(), this.interactions.start()]);
1744
+ }
1745
+ async stop() {
1746
+ await Promise.all([this.casts.stop(), this.interactions.stop()]);
1747
+ }
1748
+ }
1749
+
1750
+ // services/CastService.ts
1751
+ import {
1752
+ ChannelType as ChannelType2,
1753
+ createUniqueUuid as createUniqueUuid3,
1754
+ ModelType as ModelType2
1755
+ } from "@elizaos/core";
1756
+ function clampLimit(value, fallback, max) {
1757
+ if (!Number.isFinite(value)) {
1758
+ return fallback;
1759
+ }
1760
+ return Math.min(Math.max(1, Math.floor(value)), max);
1761
+ }
1762
+ function readContentString(content, keys) {
1763
+ const record = content;
1764
+ for (const key of keys) {
1765
+ const value = record[key];
1766
+ if (typeof value === "string" && value.trim()) {
1767
+ return value.trim();
1768
+ }
1769
+ }
1770
+ return;
1771
+ }
1772
+
1773
+ class FarcasterCastService {
1774
+ client;
1775
+ runtime;
1776
+ accountId;
1777
+ static serviceType = "ICastService";
1778
+ constructor(client, runtime, accountId = DEFAULT_FARCASTER_ACCOUNT_ID) {
1779
+ this.client = client;
1780
+ this.runtime = runtime;
1781
+ this.accountId = accountId;
1782
+ }
1783
+ getAccountId() {
1784
+ return normalizeFarcasterAccountId(this.accountId);
1785
+ }
1786
+ async getCasts(params) {
1787
+ try {
1788
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
1789
+ if (!fid) {
1790
+ this.runtime.logger.error("FARCASTER_FID is not configured");
1791
+ return [];
1792
+ }
1793
+ const { timeline } = await this.client.getTimeline({
1794
+ fid,
1795
+ pageSize: params.limit || 50
1796
+ });
1797
+ return timeline.map((cast) => this.castToFarcasterCast(cast, params.agentId));
1798
+ } catch (error) {
1799
+ this.runtime.logger.error(`Failed to get casts: ${JSON.stringify({ params, error })}`);
1800
+ return [];
1801
+ }
1802
+ }
1803
+ async createCast(params) {
1804
+ try {
1805
+ let castText = params.text;
1806
+ const media = (params.media ?? []).filter((url) => typeof url === "string" && url.trim().length > 0);
1807
+ if ((!castText || castText.trim() === "") && media.length === 0) {
1808
+ castText = await this.generateCastContent();
1809
+ }
1810
+ if (castText.length > 320) {
1811
+ castText = await this.truncateCast(castText);
1812
+ }
1813
+ const casts = await this.client.sendCast({
1814
+ content: { text: castText },
1815
+ inReplyTo: params.replyTo ? { hash: params.replyTo.hash, fid: params.replyTo.fid } : undefined,
1816
+ ...media.length > 0 ? { embeds: media } : {}
1817
+ });
1818
+ if (casts.length === 0) {
1819
+ throw new Error("No cast was created");
1820
+ }
1821
+ const cast = neynarCastToCast(casts[0]);
1822
+ const farcasterCast = {
1823
+ id: castUuid({ hash: cast.hash, agentId: params.agentId }),
1824
+ agentId: params.agentId,
1825
+ roomId: params.roomId,
1826
+ userId: cast.profile.fid.toString(),
1827
+ username: cast.profile.username,
1828
+ text: cast.text,
1829
+ timestamp: cast.timestamp.getTime(),
1830
+ inReplyTo: params.replyTo?.hash,
1831
+ media: media.map((url) => ({ url })),
1832
+ metadata: {
1833
+ accountId: this.getAccountId(),
1834
+ castHash: cast.hash,
1835
+ authorFid: cast.authorFid,
1836
+ source: FARCASTER_SOURCE,
1837
+ ...cast.threadId ? { threadId: cast.threadId } : {}
1838
+ }
1839
+ };
1840
+ await this.storeCastInMemory(params.roomId, farcasterCast);
1841
+ return farcasterCast;
1842
+ } catch (error) {
1843
+ this.runtime.logger.error(`Failed to create cast: ${JSON.stringify({ params, error })}`);
1844
+ throw error;
1845
+ }
1846
+ }
1847
+ async handleSendPost(runtime, content) {
1848
+ const requestedAccountId = normalizeFarcasterAccountId(readFarcasterAccountId(content) ?? this.getAccountId());
1849
+ if (requestedAccountId !== this.getAccountId()) {
1850
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
1851
+ }
1852
+ const text = typeof content.text === "string" ? content.text.trim() : "";
1853
+ const media = extractCastEmbedUrls(content);
1854
+ if (!text && media.length === 0) {
1855
+ throw new Error("Farcaster post connector requires non-empty text content or at least one attachment.");
1856
+ }
1857
+ const parentHash = readContentString(content, ["parentHash", "replyTo", "replyToHash"]);
1858
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
1859
+ const cast = await this.createCast({
1860
+ agentId: runtime.agentId,
1861
+ roomId: createUniqueUuid3(runtime, `farcaster:feed:${fid ?? runtime.agentId}`),
1862
+ text,
1863
+ ...media.length > 0 ? { media } : {},
1864
+ ...parentHash && fid ? { replyTo: { hash: parentHash, fid } } : {}
1865
+ });
1866
+ return this.farcasterCastToMemory(runtime, cast);
1867
+ }
1868
+ async fetchFeed(context, params = {}) {
1869
+ const requestedAccountId = normalizeFarcasterAccountId(context.accountId ?? context.metadata?.accountId ?? this.getAccountId());
1870
+ if (requestedAccountId !== this.getAccountId()) {
1871
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
1872
+ }
1873
+ const casts = await this.getCasts({
1874
+ agentId: context.runtime.agentId,
1875
+ limit: clampLimit(params.limit, 25, 100),
1876
+ cursor: params.cursor
1877
+ });
1878
+ return casts.map((cast) => this.farcasterCastToMemory(context.runtime, cast));
1879
+ }
1880
+ async searchPosts(context, params) {
1881
+ const requestedAccountId = normalizeFarcasterAccountId(context.accountId ?? context.metadata?.accountId ?? this.getAccountId());
1882
+ if (requestedAccountId !== this.getAccountId()) {
1883
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
1884
+ }
1885
+ const query = params.query.trim().toLowerCase();
1886
+ if (!query) {
1887
+ return [];
1888
+ }
1889
+ const limit = clampLimit(params.limit, 25, 100);
1890
+ const casts = await this.getCasts({
1891
+ agentId: context.runtime.agentId,
1892
+ limit: 100,
1893
+ cursor: params.cursor
1894
+ });
1895
+ return casts.filter((cast) => {
1896
+ const text = cast.text.toLowerCase();
1897
+ const username = cast.username.toLowerCase();
1898
+ return text.includes(query) || username.includes(query);
1899
+ }).slice(0, limit).map((cast) => this.farcasterCastToMemory(context.runtime, cast));
1900
+ }
1901
+ async deleteCast(params) {
1902
+ this.runtime.logger.warn(`Cast deletion is not supported by the Farcaster API: ${JSON.stringify({ castHash: params.castHash })}`);
1903
+ }
1904
+ async likeCast(params) {
1905
+ try {
1906
+ const result = await this.client.publishReaction({
1907
+ reactionType: "like",
1908
+ target: params.castHash
1909
+ });
1910
+ if (!result.success) {
1911
+ throw new Error(`Failed to like cast: ${params.castHash}`);
1912
+ }
1913
+ this.runtime.logger.info(`Liked cast: ${params.castHash}`);
1914
+ } catch (error) {
1915
+ this.runtime.logger.error(`Failed to like cast: ${JSON.stringify({ params, error })}`);
1916
+ throw error;
1917
+ }
1918
+ }
1919
+ async unlikeCast(params) {
1920
+ try {
1921
+ const result = await this.client.deleteReaction({
1922
+ reactionType: "like",
1923
+ target: params.castHash
1924
+ });
1925
+ if (!result.success) {
1926
+ throw new Error(`Failed to unlike cast: ${params.castHash}`);
1927
+ }
1928
+ this.runtime.logger.info(`Unliked cast: ${params.castHash}`);
1929
+ } catch (error) {
1930
+ this.runtime.logger.error(`Failed to unlike cast: ${JSON.stringify({ params, error })}`);
1931
+ throw error;
1932
+ }
1933
+ }
1934
+ async recast(params) {
1935
+ try {
1936
+ const result = await this.client.publishReaction({
1937
+ reactionType: "recast",
1938
+ target: params.castHash
1939
+ });
1940
+ if (!result.success) {
1941
+ throw new Error(`Failed to recast: ${params.castHash}`);
1942
+ }
1943
+ this.runtime.logger.info(`Recasted: ${params.castHash}`);
1944
+ } catch (error) {
1945
+ this.runtime.logger.error(`Failed to recast: ${JSON.stringify({ params, error })}`);
1946
+ throw error;
1947
+ }
1948
+ }
1949
+ async unrecast(params) {
1950
+ try {
1951
+ const result = await this.client.deleteReaction({
1952
+ reactionType: "recast",
1953
+ target: params.castHash
1954
+ });
1955
+ if (!result.success) {
1956
+ throw new Error(`Failed to remove recast: ${params.castHash}`);
1957
+ }
1958
+ this.runtime.logger.info(`Removed recast: ${params.castHash}`);
1959
+ } catch (error) {
1960
+ this.runtime.logger.error(`Failed to remove recast: ${JSON.stringify({ params, error })}`);
1961
+ throw error;
1962
+ }
1963
+ }
1964
+ async getMentions(params) {
1965
+ try {
1966
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
1967
+ if (!fid) {
1968
+ this.runtime.logger.error("FARCASTER_FID is not configured");
1969
+ return [];
1970
+ }
1971
+ const mentions = await this.client.getMentions({
1972
+ fid,
1973
+ pageSize: params.limit || 20
1974
+ });
1975
+ return mentions.map((castWithInteractions) => {
1976
+ const cast = neynarCastToCast(castWithInteractions);
1977
+ return this.castToFarcasterCast(cast, params.agentId);
1978
+ });
1979
+ } catch (error) {
1980
+ this.runtime.logger.error(`Failed to get mentions: ${JSON.stringify({ params, error })}`);
1981
+ return [];
1982
+ }
1983
+ }
1984
+ async generateCastContent() {
1985
+ const prompt = `Generate an interesting and engaging Farcaster cast. It should be conversational, authentic, and under 320 characters. Topics can include technology, AI, crypto, decentralized social media, or general observations about life.`;
1986
+ try {
1987
+ const response = await this.runtime.useModel(ModelType2.TEXT_SMALL, {
1988
+ prompt,
1989
+ maxTokens: 100
1990
+ });
1991
+ return response;
1992
+ } catch (error) {
1993
+ this.runtime.logger.error(`Failed to generate cast content: ${JSON.stringify({ error })}`);
1994
+ return "Hello Farcaster! \uD83D\uDC4B";
1995
+ }
1996
+ }
1997
+ async truncateCast(text) {
1998
+ const prompt = `Shorten this text to under 320 characters while keeping the main message intact: "${text}"`;
1999
+ try {
2000
+ const response = await this.runtime.useModel(ModelType2.TEXT_SMALL, {
2001
+ prompt,
2002
+ maxTokens: 100
2003
+ });
2004
+ const truncated = response;
2005
+ if (truncated.length > 320) {
2006
+ return `${truncated.substring(0, 317)}...`;
2007
+ }
2008
+ return truncated;
2009
+ } catch (error) {
2010
+ this.runtime.logger.error(`Failed to truncate cast: ${JSON.stringify({ error })}`);
2011
+ return `${text.substring(0, 317)}...`;
2012
+ }
2013
+ }
2014
+ async storeCastInMemory(roomId, cast) {
2015
+ try {
2016
+ const entityId = createUniqueUuid3(this.runtime, cast.userId);
2017
+ const memory = {
2018
+ id: createUniqueUuid3(this.runtime, cast.id),
2019
+ agentId: this.runtime.agentId,
2020
+ entityId,
2021
+ content: {
2022
+ text: cast.text,
2023
+ castHash: String(cast.metadata?.castHash || ""),
2024
+ castId: cast.id,
2025
+ author: cast.username,
2026
+ timestamp: cast.timestamp,
2027
+ accountId: this.getAccountId()
2028
+ },
2029
+ metadata: {
2030
+ accountId: this.getAccountId()
2031
+ },
2032
+ roomId,
2033
+ createdAt: Date.now()
2034
+ };
2035
+ await this.runtime.createMemory(memory, "farcaster_casts");
2036
+ } catch (error) {
2037
+ this.runtime.logger.error(`Failed to store cast in memory: ${JSON.stringify({ error })}`);
2038
+ }
2039
+ }
2040
+ farcasterCastToMemory(runtime, cast) {
2041
+ const authorId = cast.userId || "unknown";
2042
+ const entityId = authorId === runtime.agentId ? runtime.agentId : createUniqueUuid3(runtime, `farcaster:user:${authorId}`);
2043
+ const roomId = cast.roomId || createUniqueUuid3(runtime, `farcaster:feed:${authorId}`);
2044
+ const castHash = typeof cast.metadata?.castHash === "string" ? cast.metadata.castHash : cast.id;
2045
+ return {
2046
+ id: createUniqueUuid3(runtime, `farcaster:cast:${castHash}`),
2047
+ agentId: runtime.agentId,
2048
+ entityId,
2049
+ roomId,
2050
+ createdAt: cast.timestamp || Date.now(),
2051
+ content: {
2052
+ text: cast.text,
2053
+ source: FARCASTER_SOURCE,
2054
+ channelType: ChannelType2.FEED,
2055
+ ...cast.inReplyTo ? { inReplyTo: createUniqueUuid3(runtime, `farcaster:cast:${cast.inReplyTo}`) } : {}
2056
+ },
2057
+ metadata: {
2058
+ type: "message",
2059
+ source: FARCASTER_SOURCE,
2060
+ accountId: this.getAccountId(),
2061
+ provider: FARCASTER_SOURCE,
2062
+ timestamp: cast.timestamp,
2063
+ fromBot: entityId === runtime.agentId,
2064
+ messageIdFull: castHash,
2065
+ chatType: ChannelType2.FEED,
2066
+ sender: {
2067
+ id: authorId,
2068
+ username: cast.username
2069
+ },
2070
+ farcaster: {
2071
+ accountId: this.getAccountId(),
2072
+ castId: cast.id,
2073
+ castHash,
2074
+ authorFid: cast.metadata?.authorFid,
2075
+ username: cast.username,
2076
+ inReplyTo: cast.inReplyTo,
2077
+ metrics: {
2078
+ recasts: cast.metadata?.recasts,
2079
+ replies: cast.metadata?.replies,
2080
+ likes: cast.metadata?.likes
2081
+ },
2082
+ ...cast.metadata ?? {}
2083
+ }
2084
+ }
2085
+ };
2086
+ }
2087
+ castToFarcasterCast(cast, agentId) {
2088
+ return {
2089
+ id: castUuid({ hash: cast.hash, agentId }),
2090
+ agentId,
2091
+ roomId: createUniqueUuid3(this.runtime, cast.threadId || cast.hash),
2092
+ userId: cast.profile.fid.toString(),
2093
+ username: cast.profile.username,
2094
+ text: cast.text,
2095
+ timestamp: cast.timestamp.getTime(),
2096
+ media: [],
2097
+ metadata: {
2098
+ castHash: cast.hash,
2099
+ authorFid: cast.authorFid,
2100
+ source: FARCASTER_SOURCE,
2101
+ accountId: this.getAccountId(),
2102
+ ...cast.threadId ? { threadId: cast.threadId } : {},
2103
+ ...cast.stats ? {
2104
+ recasts: cast.stats.recasts,
2105
+ replies: cast.stats.replies,
2106
+ likes: cast.stats.likes
2107
+ } : {}
2108
+ }
2109
+ };
2110
+ }
2111
+ }
2112
+
2113
+ // services/MessageService.ts
2114
+ import { createUniqueUuid as createUniqueUuid4 } from "@elizaos/core";
2115
+ class FarcasterMessageService {
2116
+ client;
2117
+ runtime;
2118
+ accountId;
2119
+ constructor(client, runtime, accountId = DEFAULT_FARCASTER_ACCOUNT_ID) {
2120
+ this.client = client;
2121
+ this.runtime = runtime;
2122
+ this.accountId = accountId;
2123
+ }
2124
+ getAccountId() {
2125
+ return normalizeFarcasterAccountId(this.accountId);
2126
+ }
2127
+ castToMessage(cast, agentId, extraMetadata) {
2128
+ return {
2129
+ id: castUuid({ hash: cast.hash, agentId }),
2130
+ agentId,
2131
+ roomId: createUniqueUuid4(this.runtime, cast.threadId || cast.hash),
2132
+ userId: cast.profile.fid.toString(),
2133
+ username: cast.profile.username,
2134
+ text: cast.text,
2135
+ type: cast.inReplyTo ? "REPLY" /* REPLY */ : "CAST" /* CAST */,
2136
+ timestamp: cast.timestamp.getTime(),
2137
+ inReplyTo: cast.inReplyTo ? castUuid({ hash: cast.inReplyTo.hash, agentId }) : undefined,
2138
+ metadata: {
2139
+ source: FARCASTER_SOURCE,
2140
+ accountId: this.getAccountId(),
2141
+ castHash: cast.hash,
2142
+ threadId: cast.threadId,
2143
+ authorFid: cast.authorFid,
2144
+ ...extraMetadata || {}
2145
+ }
2146
+ };
2147
+ }
2148
+ async getMessages(options) {
2149
+ try {
2150
+ const { agentId, roomId, limit = 20 } = options;
2151
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
2152
+ if (!fid) {
2153
+ this.runtime.logger.error("[Farcaster] FARCASTER_FID is not configured");
2154
+ return [];
2155
+ }
2156
+ const { timeline } = await this.client.getTimeline({
2157
+ fid,
2158
+ pageSize: limit
2159
+ });
2160
+ const messages = timeline.map((cast) => this.castToMessage(cast, agentId)).filter((message) => {
2161
+ if (roomId) {
2162
+ return message.roomId === roomId;
2163
+ }
2164
+ return true;
2165
+ });
2166
+ return messages;
2167
+ } catch (error) {
2168
+ const err = error instanceof Error ? error : new Error(String(error));
2169
+ this.runtime.logger.error(err, "[Farcaster] Error fetching messages");
2170
+ return [];
2171
+ }
2172
+ }
2173
+ async sendMessage(options) {
2174
+ try {
2175
+ const requestedAccountId = normalizeFarcasterAccountId(options.accountId ?? readFarcasterAccountId(options.metadata) ?? this.getAccountId());
2176
+ if (requestedAccountId !== this.getAccountId()) {
2177
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
2178
+ }
2179
+ const { text, type, roomId, replyToId, agentId } = options;
2180
+ let inReplyTo;
2181
+ if (replyToId && type === "REPLY" /* REPLY */) {
2182
+ const parentHash = typeof options.metadata?.parentHash === "string" ? options.metadata.parentHash : replyToId;
2183
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
2184
+ if (!fid) {
2185
+ throw new Error("FARCASTER_FID is not configured");
2186
+ }
2187
+ inReplyTo = {
2188
+ hash: parentHash,
2189
+ fid
2190
+ };
2191
+ }
2192
+ const casts = await this.client.sendCast({
2193
+ content: { text },
2194
+ inReplyTo
2195
+ });
2196
+ if (casts.length === 0) {
2197
+ throw new Error("No cast was created");
2198
+ }
2199
+ const cast = neynarCastToCast(casts[0]);
2200
+ const message = this.castToMessage(cast, agentId, options.metadata);
2201
+ message.roomId = roomId;
2202
+ message.type = type;
2203
+ const postGeneratedPayload = {
2204
+ runtime: this.runtime,
2205
+ source: FARCASTER_SOURCE,
2206
+ castHash: cast.hash,
2207
+ ...cast.threadId ? { threadId: cast.threadId } : {},
2208
+ messageId: message.id,
2209
+ roomId,
2210
+ accountId: this.getAccountId()
2211
+ };
2212
+ await this.runtime.emitEvent("FARCASTER_POST_GENERATED" /* POST_GENERATED */, postGeneratedPayload);
2213
+ return message;
2214
+ } catch (error) {
2215
+ const err = error instanceof Error ? error : new Error(String(error));
2216
+ this.runtime.logger.error(err, "[Farcaster] Error sending message");
2217
+ throw err;
2218
+ }
2219
+ }
2220
+ async deleteMessage(_messageId, _agentId) {
2221
+ this.runtime.logger.warn("[Farcaster] Cast deletion is not supported by the Farcaster API");
2222
+ }
2223
+ async getMessage(messageId, agentId) {
2224
+ try {
2225
+ const castHash = messageId;
2226
+ const cast = await this.client.getCast(castHash);
2227
+ const farcasterCast = neynarCastToCast(cast);
2228
+ return this.castToMessage(farcasterCast, agentId);
2229
+ } catch (error) {
2230
+ const err = error instanceof Error ? error : new Error(String(error));
2231
+ this.runtime.logger.error(err, "[Farcaster] Error fetching message");
2232
+ return null;
2233
+ }
2234
+ }
2235
+ async getThread(params) {
2236
+ try {
2237
+ const thread = [];
2238
+ const visited = new Set;
2239
+ let currentHash = params.castHash;
2240
+ while (currentHash) {
2241
+ if (visited.has(currentHash)) {
2242
+ break;
2243
+ }
2244
+ visited.add(currentHash);
2245
+ const cast = neynarCastToCast(await this.client.getCast(currentHash));
2246
+ thread.unshift(this.castToMessage(cast, params.agentId));
2247
+ currentHash = cast.inReplyTo?.hash;
2248
+ }
2249
+ return thread;
2250
+ } catch (error) {
2251
+ const err = error instanceof Error ? error : new Error(String(error));
2252
+ this.runtime.logger.error(err, "[Farcaster] Error fetching thread");
2253
+ return [];
2254
+ }
2255
+ }
2256
+ async markAsRead(_messageIds, _agentId) {
2257
+ this.runtime.logger.debug("[Farcaster] Mark as read is not applicable for Farcaster casts");
2258
+ }
2259
+ }
2260
+
2261
+ // services/FarcasterService.ts
2262
+ class FarcasterService extends Service {
2263
+ static instance;
2264
+ agents = new Map;
2265
+ static serviceType = FARCASTER_SERVICE_NAME;
2266
+ description = "Farcaster integration service for sending and receiving casts";
2267
+ capabilityDescription = "The agent is able to send and receive messages on farcaster";
2268
+ static getInstance() {
2269
+ if (!FarcasterService.instance) {
2270
+ FarcasterService.instance = new FarcasterService;
2271
+ }
2272
+ return FarcasterService.instance;
2273
+ }
2274
+ async initialize(runtime) {
2275
+ await FarcasterService.start(runtime);
2276
+ }
2277
+ static async start(runtime) {
2278
+ const service = FarcasterService.getInstance();
2279
+ if (service.agents.has(runtime.agentId)) {
2280
+ runtime.logger.warn({ agentId: runtime.agentId }, "Farcaster service already started");
2281
+ return service;
2282
+ }
2283
+ if (!hasFarcasterEnabled(runtime)) {
2284
+ runtime.logger.debug({ agentId: runtime.agentId }, "Farcaster service not enabled");
2285
+ return service;
2286
+ }
2287
+ const accounts = {
2288
+ defaultAccountId: normalizeFarcasterAccountId(resolveDefaultFarcasterAccountId(runtime)),
2289
+ managers: new Map,
2290
+ messageServices: new Map,
2291
+ castServices: new Map
2292
+ };
2293
+ service.agents.set(runtime.agentId, accounts);
2294
+ for (const accountId of listFarcasterAccountIds(runtime)) {
2295
+ if (!hasFarcasterEnabled(runtime, accountId)) {
2296
+ continue;
2297
+ }
2298
+ const farcasterConfig = validateFarcasterConfig(runtime, accountId);
2299
+ const manager = new FarcasterAgentManager(runtime, farcasterConfig);
2300
+ accounts.managers.set(accountId, manager);
2301
+ accounts.messageServices.set(accountId, new FarcasterMessageService(manager.client, runtime, accountId));
2302
+ accounts.castServices.set(accountId, new FarcasterCastService(manager.client, runtime, accountId));
2303
+ await manager.start();
2304
+ runtime.logger.success({ agentId: runtime.agentId, accountId }, "Farcaster client started");
2305
+ }
2306
+ return service;
2307
+ }
2308
+ static async stop(runtime) {
2309
+ const service = FarcasterService.getInstance();
2310
+ const accounts = service.agents.get(runtime.agentId);
2311
+ if (accounts) {
2312
+ for (const manager of accounts.managers.values()) {
2313
+ await manager.stop();
2314
+ }
2315
+ service.agents.delete(runtime.agentId);
2316
+ runtime.logger.info({ agentId: runtime.agentId }, "Farcaster client stopped");
2317
+ } else {
2318
+ runtime.logger.debug({ agentId: runtime.agentId }, "Farcaster service not running");
2319
+ }
2320
+ }
2321
+ static registerSendHandlers(runtime, serviceInstance) {
2322
+ const accounts = serviceInstance.agents.get(runtime.agentId);
2323
+ if (!accounts || accounts.castServices.size === 0) {
2324
+ runtime.logger.warn({ src: "plugin:farcaster", agentId: runtime.agentId }, "Cannot register Farcaster post connector; cast service is not initialized");
2325
+ return;
2326
+ }
2327
+ const withPostConnector = runtime;
2328
+ if (typeof withPostConnector.registerPostConnector !== "function") {
2329
+ return;
2330
+ }
2331
+ for (const castService of accounts.castServices.values()) {
2332
+ const accountId = castService.getAccountId();
2333
+ withPostConnector.registerPostConnector({
2334
+ source: "farcaster",
2335
+ accountId,
2336
+ label: "Farcaster",
2337
+ description: "Farcaster public cast connector for publishing casts and reading or searching the authenticated account's recent feed.",
2338
+ capabilities: ["post", "fetch_feed", "search_posts"],
2339
+ contexts: ["social", "social_posting", "connectors"],
2340
+ metadata: {
2341
+ accountId,
2342
+ service: FARCASTER_SERVICE_NAME
2343
+ },
2344
+ postHandler: castService.handleSendPost.bind(castService),
2345
+ fetchFeed: castService.fetchFeed.bind(castService),
2346
+ searchPosts: castService.searchPosts.bind(castService),
2347
+ contentShaping: {
2348
+ systemPromptFragment: "For Farcaster casts, write a conversational public cast under 320 characters. If replying, keep enough context for a public thread.",
2349
+ constraints: {
2350
+ maxLength: 320,
2351
+ supportsMarkdown: false,
2352
+ channelType: ChannelType3.FEED
2353
+ }
2354
+ }
2355
+ });
2356
+ }
2357
+ runtime.logger.info({ src: "plugin:farcaster", agentId: runtime.agentId }, "Registered Farcaster post connector");
2358
+ }
2359
+ async stop() {
2360
+ for (const [agentId, accounts] of Array.from(this.agents.entries())) {
2361
+ const runtime = accounts.managers.values().next().value?.runtime;
2362
+ runtime?.logger.debug("Stopping Farcaster service");
2363
+ try {
2364
+ if (runtime) {
2365
+ await FarcasterService.stop(runtime);
2366
+ } else {
2367
+ this.agents.delete(agentId);
2368
+ }
2369
+ } catch (error) {
2370
+ runtime?.logger.error({ agentId, error }, "Error stopping Farcaster service");
2371
+ }
2372
+ }
2373
+ }
2374
+ getMessageService(agentId, accountId) {
2375
+ return this.getMessageServiceForAccount(accountId, agentId);
2376
+ }
2377
+ getCastService(agentId, accountId) {
2378
+ return this.getCastServiceForAccount(accountId, agentId);
2379
+ }
2380
+ getMessageServiceForAccount(accountId, agentId) {
2381
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2382
+ if (!resolvedAgentId)
2383
+ return;
2384
+ const accounts = this.agents.get(resolvedAgentId);
2385
+ if (!accounts)
2386
+ return;
2387
+ const id = accountId ? normalizeFarcasterAccountId(accountId) : accounts.defaultAccountId;
2388
+ return accounts.messageServices.get(id);
2389
+ }
2390
+ getCastServiceForAccount(accountId, agentId) {
2391
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2392
+ if (!resolvedAgentId)
2393
+ return;
2394
+ const accounts = this.agents.get(resolvedAgentId);
2395
+ if (!accounts)
2396
+ return;
2397
+ const id = accountId ? normalizeFarcasterAccountId(accountId) : accounts.defaultAccountId;
2398
+ return accounts.castServices.get(id);
2399
+ }
2400
+ getManagerForAccount(accountId, agentId) {
2401
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2402
+ if (!resolvedAgentId)
2403
+ return;
2404
+ const accounts = this.agents.get(resolvedAgentId);
2405
+ if (!accounts)
2406
+ return;
2407
+ const id = accountId ? normalizeFarcasterAccountId(accountId) : accounts.defaultAccountId;
2408
+ return accounts.managers.get(id);
2409
+ }
2410
+ getDefaultAccountId(agentId) {
2411
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2412
+ return resolvedAgentId ? this.agents.get(resolvedAgentId)?.defaultAccountId : undefined;
2413
+ }
2414
+ listAccountIds(agentId) {
2415
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2416
+ const accounts = resolvedAgentId ? this.agents.get(resolvedAgentId) : undefined;
2417
+ return accounts ? Array.from(accounts.managers.keys()) : [];
2418
+ }
2419
+ getManagersForAgent(agentId) {
2420
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2421
+ const accounts = resolvedAgentId ? this.agents.get(resolvedAgentId) : undefined;
2422
+ return new Map(accounts?.managers ?? []);
2423
+ }
2424
+ async healthCheck() {
2425
+ const managerStatuses = {};
2426
+ let overallHealthy = true;
2427
+ for (const [agentId, accounts] of Array.from(this.agents.entries())) {
2428
+ managerStatuses[agentId] = {};
2429
+ for (const [accountId, manager] of Array.from(accounts.managers.entries())) {
2430
+ try {
2431
+ const fid = getFarcasterFid(manager.runtime, accountId);
2432
+ if (!fid) {
2433
+ throw new Error("FARCASTER_FID not configured");
2434
+ }
2435
+ const profile = await manager.client.getProfile(fid);
2436
+ managerStatuses[agentId][accountId] = {
2437
+ status: "healthy",
2438
+ fid: profile.fid,
2439
+ username: profile.username
2440
+ };
2441
+ } catch (error) {
2442
+ managerStatuses[agentId][accountId] = {
2443
+ status: "unhealthy",
2444
+ error: error instanceof Error ? error.message : "Unknown error"
2445
+ };
2446
+ overallHealthy = false;
2447
+ }
2448
+ }
2449
+ }
2450
+ return {
2451
+ healthy: overallHealthy,
2452
+ details: {
2453
+ activeManagers: Array.from(this.agents.values()).reduce((total, accounts) => total + accounts.managers.size, 0),
2454
+ managerStatuses
2455
+ }
2456
+ };
2457
+ }
2458
+ getActiveManagers() {
2459
+ return new Map(Array.from(this.agents.entries()).flatMap(([agentId, accounts]) => Array.from(accounts.managers.entries()).map(([accountId, manager]) => [`${agentId}:${accountId}`, manager])));
2460
+ }
2461
+ firstAgentId() {
2462
+ return this.agents.keys().next().value;
2463
+ }
2464
+ }
2465
+
2466
+ // workflow-credential-provider.ts
2467
+ import { Service as Service2 } from "@elizaos/core";
2468
+ var WORKFLOW_CREDENTIAL_PROVIDER_TYPE = "workflow_credential_provider";
2469
+ var SUPPORTED = ["httpHeaderAuth"];
2470
+
2471
+ class FarcasterWorkflowCredentialProvider extends Service2 {
2472
+ static serviceType = WORKFLOW_CREDENTIAL_PROVIDER_TYPE;
2473
+ capabilityDescription = "Supplies Farcaster (Neynar API) credentials to the workflow plugin.";
2474
+ static async start(runtime) {
2475
+ return new FarcasterWorkflowCredentialProvider(runtime);
2476
+ }
2477
+ async stop() {}
2478
+ async resolve(_userId, credType) {
2479
+ if (credType !== "httpHeaderAuth")
2480
+ return null;
2481
+ let neynarApiKey;
2482
+ try {
2483
+ neynarApiKey = this.runtime.getSetting("FARCASTER_NEYNAR_API_KEY");
2484
+ } catch {
2485
+ return null;
2486
+ }
2487
+ if (!neynarApiKey?.trim())
2488
+ return null;
2489
+ return {
2490
+ status: "credential_data",
2491
+ data: { name: "api_key", value: neynarApiKey.trim() }
2492
+ };
2493
+ }
2494
+ checkCredentialTypes(credTypes) {
2495
+ return {
2496
+ supported: credTypes.filter((t) => SUPPORTED.includes(t)),
2497
+ unsupported: credTypes.filter((t) => !SUPPORTED.includes(t))
2498
+ };
2499
+ }
2500
+ }
2501
+
2502
+ // index.ts
2503
+ var farcasterPlugin = {
2504
+ name: "farcaster",
2505
+ description: "Farcaster client plugin for sending and receiving casts",
2506
+ services: [FarcasterService, FarcasterWorkflowCredentialProvider],
2507
+ actions: [],
2508
+ providers: farcasterProviders,
2509
+ routes: farcasterWebhookRoutes,
2510
+ autoEnable: {
2511
+ connectorKeys: ["farcaster"]
2512
+ },
2513
+ async init(_config, runtime) {
2514
+ try {
2515
+ const manager = getConnectorAccountManager(runtime);
2516
+ manager.registerProvider(createFarcasterConnectorAccountProvider(runtime));
2517
+ } catch (err) {
2518
+ logger.warn({
2519
+ src: "plugin:farcaster",
2520
+ err: err instanceof Error ? err.message : String(err)
2521
+ }, "Failed to register Farcaster provider with ConnectorAccountManager");
2522
+ }
2523
+ },
2524
+ async dispose(runtime) {
2525
+ await FarcasterService.stop(runtime);
2526
+ }
2527
+ };
2528
+ var plugin_farcaster_default = farcasterPlugin;
2529
+ export {
2530
+ resolveDefaultFarcasterAccountId,
2531
+ readFarcasterAccountId,
2532
+ normalizeFarcasterAccountId,
2533
+ listFarcasterAccountIds,
2534
+ isEmbedUrl,
2535
+ isEmbedCast,
2536
+ farcasterPlugin,
2537
+ plugin_farcaster_default as default,
2538
+ createFarcasterConnectorAccountProvider,
2539
+ FarcasterService,
2540
+ FarcasterClient,
2541
+ FARCASTER_PROVIDER_ID,
2542
+ EmbedManager,
2543
+ DEFAULT_FARCASTER_ACCOUNT_ID
2544
+ };
2545
+
2546
+ //# debugId=E5B04D3BB7E8528A64756E2164756E21