@elizaos/plugin-farcaster 1.7.2 → 2.0.0-alpha.1

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