@elizaos/plugin-farcaster 2.0.0-alpha.7 → 2.0.0-beta.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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +108 -0
  3. package/auto-enable.ts +21 -0
  4. package/dist/actions/index.d.ts +2 -0
  5. package/dist/actions/index.d.ts.map +1 -0
  6. package/dist/auto-enable.d.ts +4 -0
  7. package/dist/auto-enable.d.ts.map +1 -0
  8. package/dist/browser/index.d.ts +2 -2
  9. package/dist/cjs/index.d.ts +2 -2
  10. package/dist/cjs/index.node.cjs +870 -719
  11. package/dist/cjs/index.node.js.map +23 -26
  12. package/dist/client/FarcasterClient.d.ts +41 -0
  13. package/dist/client/FarcasterClient.d.ts.map +1 -0
  14. package/dist/client/index.d.ts +2 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/connector-account-provider.d.ts +16 -0
  17. package/dist/connector-account-provider.d.ts.map +1 -0
  18. package/dist/generated/specs/spec-helpers.d.ts +36 -0
  19. package/dist/generated/specs/spec-helpers.d.ts.map +1 -0
  20. package/dist/generated/specs/specs.d.ts +48 -0
  21. package/dist/generated/specs/specs.d.ts.map +1 -0
  22. package/dist/index.browser.d.ts +11 -0
  23. package/dist/index.browser.d.ts.map +1 -0
  24. package/dist/index.d.ts +10 -2
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.node.d.ts +3 -0
  27. package/dist/index.node.d.ts.map +1 -0
  28. package/dist/managers/AgentManager.d.ts +20 -0
  29. package/dist/managers/AgentManager.d.ts.map +1 -0
  30. package/dist/managers/CastManager.d.ts +25 -0
  31. package/dist/managers/CastManager.d.ts.map +1 -0
  32. package/dist/managers/EmbedManager.d.ts +40 -0
  33. package/dist/managers/EmbedManager.d.ts.map +1 -0
  34. package/dist/managers/InteractionManager.d.ts +32 -0
  35. package/dist/managers/InteractionManager.d.ts.map +1 -0
  36. package/dist/managers/InteractionProcessor.d.ts +10 -0
  37. package/dist/managers/InteractionProcessor.d.ts.map +1 -0
  38. package/dist/managers/InteractionSource.d.ts +38 -0
  39. package/dist/managers/InteractionSource.d.ts.map +1 -0
  40. package/dist/managers/index.d.ts +7 -0
  41. package/dist/managers/index.d.ts.map +1 -0
  42. package/dist/node/index.d.ts +2 -2
  43. package/dist/node/index.node.d.ts +2 -0
  44. package/dist/node/index.node.js +830 -704
  45. package/dist/node/index.node.js.map +23 -26
  46. package/dist/providers/index.d.ts +4 -0
  47. package/dist/providers/index.d.ts.map +1 -0
  48. package/dist/providers/profileProvider.d.ts +3 -0
  49. package/dist/providers/profileProvider.d.ts.map +1 -0
  50. package/dist/routes/webhook.d.ts +3 -0
  51. package/dist/routes/webhook.d.ts.map +1 -0
  52. package/dist/services/CastService.d.ts +133 -0
  53. package/dist/services/CastService.d.ts.map +1 -0
  54. package/dist/services/FarcasterService.d.ts +32 -0
  55. package/dist/services/FarcasterService.d.ts.map +1 -0
  56. package/dist/services/MessageService.d.ts +54 -0
  57. package/dist/services/MessageService.d.ts.map +1 -0
  58. package/dist/services/index.d.ts +4 -0
  59. package/dist/services/index.d.ts.map +1 -0
  60. package/dist/types/index.d.ts +119 -0
  61. package/dist/types/index.d.ts.map +1 -0
  62. package/dist/utils/asyncqueue.d.ts +13 -0
  63. package/dist/utils/asyncqueue.d.ts.map +1 -0
  64. package/dist/utils/callbacks.d.ts +14 -0
  65. package/dist/utils/callbacks.d.ts.map +1 -0
  66. package/dist/utils/config.d.ts +14 -0
  67. package/dist/utils/config.d.ts.map +1 -0
  68. package/dist/utils/index.d.ts +25 -0
  69. package/dist/utils/index.d.ts.map +1 -0
  70. package/dist/utils/prompts.d.ts +5 -0
  71. package/dist/utils/prompts.d.ts.map +1 -0
  72. package/dist/workflow-credential-provider.d.ts +21 -0
  73. package/dist/workflow-credential-provider.d.ts.map +1 -0
  74. package/package.json +20 -9
@@ -1,359 +1,41 @@
1
- // actions/replyCast.ts
2
- import { ModelType } from "@elizaos/core";
3
-
4
- // generated/specs/specs.ts
5
- var coreActionsSpec = {
6
- version: "1.0.0",
7
- actions: [
8
- {
9
- name: "SEND_CAST",
10
- description: "Posts a cast (message) on Farcaster",
11
- similes: ["POST_CAST", "FARCASTER_POST", "CAST", "SHARE_ON_FARCASTER", "ANNOUNCE"],
12
- parameters: []
13
- },
14
- {
15
- name: "REPLY_TO_CAST",
16
- description: "Replies to a cast on Farcaster",
17
- similes: ["REPLY_CAST", "RESPOND_CAST", "ANSWER_CAST", "COMMENT_CAST"],
18
- parameters: []
19
- }
20
- ]
21
- };
22
- var allActionsSpec = {
23
- version: "1.0.0",
24
- actions: [
25
- {
26
- name: "SEND_CAST",
27
- description: "Posts a cast (message) on Farcaster",
28
- similes: ["POST_CAST", "FARCASTER_POST", "CAST", "SHARE_ON_FARCASTER", "ANNOUNCE"],
29
- parameters: []
30
- },
31
- {
32
- name: "REPLY_TO_CAST",
33
- description: "Replies to a cast on Farcaster",
34
- similes: ["REPLY_CAST", "RESPOND_CAST", "ANSWER_CAST", "COMMENT_CAST"],
35
- parameters: []
36
- }
37
- ]
38
- };
39
- var coreProvidersSpec = {
40
- version: "1.0.0",
41
- providers: [
42
- {
43
- name: "farcasterProfile",
44
- description: "Provides information about the agent",
45
- dynamic: true
46
- },
47
- {
48
- name: "farcasterThread",
49
- description: "Provides thread context for Farcaster casts so the agent can reference the full conversation.",
50
- dynamic: true
51
- },
52
- {
53
- name: "farcasterTimeline",
54
- description: "Provides recent casts from the agent",
55
- dynamic: true
56
- }
57
- ]
58
- };
59
- var allProvidersSpec = {
60
- version: "1.0.0",
61
- providers: [
62
- {
63
- name: "farcasterProfile",
64
- description: "Provides information about the agent",
65
- dynamic: true
66
- },
67
- {
68
- name: "farcasterThread",
69
- description: "Provides thread context for Farcaster casts so the agent can reference the full conversation.",
70
- dynamic: true
71
- },
72
- {
73
- name: "farcasterTimeline",
74
- description: "Provides recent casts from the agent",
75
- dynamic: true
76
- }
77
- ]
78
- };
79
- var coreEvaluatorsSpec = {
80
- version: "1.0.0",
81
- evaluators: []
82
- };
83
- var allEvaluatorsSpec = {
84
- version: "1.0.0",
85
- evaluators: []
86
- };
87
- var coreActionDocs = coreActionsSpec.actions;
88
- var allActionDocs = allActionsSpec.actions;
89
- var coreProviderDocs = coreProvidersSpec.providers;
90
- var allProviderDocs = allProvidersSpec.providers;
91
- var coreEvaluatorDocs = coreEvaluatorsSpec.evaluators;
92
- var allEvaluatorDocs = allEvaluatorsSpec.evaluators;
1
+ // index.ts
2
+ import { getConnectorAccountManager, logger } from "@elizaos/core";
93
3
 
94
- // generated/specs/spec-helpers.ts
95
- var coreActionMap = new Map(coreActionDocs.map((doc) => [doc.name, doc]));
96
- var allActionMap = new Map(allActionDocs.map((doc) => [doc.name, doc]));
97
- var coreProviderMap = new Map(coreProviderDocs.map((doc) => [doc.name, doc]));
98
- var allProviderMap = new Map(allProviderDocs.map((doc) => [doc.name, doc]));
99
- var coreEvaluatorMap = new Map(coreEvaluatorDocs.map((doc) => [doc.name, doc]));
100
- var allEvaluatorMap = new Map(allEvaluatorDocs.map((doc) => [doc.name, doc]));
101
- function getActionSpec(name) {
102
- return coreActionMap.get(name) ?? allActionMap.get(name);
103
- }
104
- function requireActionSpec(name) {
105
- const spec = getActionSpec(name);
106
- if (!spec) {
107
- throw new Error(`Action spec not found: ${name}`);
108
- }
109
- return spec;
110
- }
111
- function getProviderSpec(name) {
112
- return coreProviderMap.get(name) ?? allProviderMap.get(name);
113
- }
114
- function requireProviderSpec(name) {
115
- const spec = getProviderSpec(name);
116
- if (!spec) {
117
- throw new Error(`Provider spec not found: ${name}`);
118
- }
119
- return spec;
120
- }
4
+ // utils/config.ts
5
+ import { parseBooleanFromText } from "@elizaos/core";
6
+ import { z as z3 } from "zod";
121
7
 
122
8
  // types/index.ts
123
- import { z } from "zod";
9
+ import * as zod from "zod";
10
+ var z2 = zod.z ?? zod;
124
11
  var DEFAULT_MAX_CAST_LENGTH = 320;
125
12
  var DEFAULT_POLL_INTERVAL = 120;
126
13
  var DEFAULT_CAST_INTERVAL_MIN = 90;
127
14
  var DEFAULT_CAST_INTERVAL_MAX = 180;
128
15
  var DEFAULT_CAST_CACHE_TTL = 1000 * 30 * 60;
129
16
  var DEFAULT_CAST_CACHE_SIZE = 9000;
130
- var FarcasterConfigSchema = z.object({
131
- FARCASTER_DRY_RUN: z.union([z.boolean(), z.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
132
- FARCASTER_FID: z.number().int().min(1, "Farcaster fid is required"),
133
- MAX_CAST_LENGTH: z.number().int().default(DEFAULT_MAX_CAST_LENGTH),
134
- FARCASTER_POLL_INTERVAL: z.number().int().default(DEFAULT_POLL_INTERVAL),
135
- FARCASTER_MODE: z.enum(["polling", "webhook"]).default("polling"),
136
- ENABLE_CAST: z.union([z.boolean(), z.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
137
- CAST_INTERVAL_MIN: z.number().int(),
138
- CAST_INTERVAL_MAX: z.number().int(),
139
- ENABLE_ACTION_PROCESSING: z.union([z.boolean(), z.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
140
- ACTION_INTERVAL: z.number().int(),
141
- CAST_IMMEDIATELY: z.union([z.boolean(), z.string()]).transform((val) => typeof val === "string" ? val.toLowerCase() === "true" : val),
142
- MAX_ACTIONS_PROCESSING: z.number().int(),
143
- FARCASTER_SIGNER_UUID: z.string().min(1, "FARCASTER_SIGNER_UUID is not set"),
144
- FARCASTER_NEYNAR_API_KEY: z.string().min(1, "FARCASTER_NEYNAR_API_KEY is not set"),
145
- FARCASTER_HUB_URL: z.string().min(1, "FARCASTER_HUB_URL is not set")
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")
146
33
  });
147
34
  var FARCASTER_SERVICE_NAME = "farcaster";
148
35
  var FARCASTER_SOURCE = "farcaster";
149
36
 
150
- // actions/replyCast.ts
151
- var spec = requireActionSpec("REPLY_TO_CAST");
152
- var replyCastAction = {
153
- name: spec.name,
154
- description: spec.description,
155
- examples: [
156
- [
157
- {
158
- name: "user",
159
- content: {
160
- text: "Someone asked about ElizaOS on Farcaster, can you reply?"
161
- }
162
- },
163
- {
164
- name: "assistant",
165
- content: {
166
- text: "I'll reply to their question about ElizaOS.",
167
- actions: ["REPLY_TO_CAST"]
168
- }
169
- }
170
- ],
171
- [
172
- {
173
- name: "user",
174
- content: { text: "Reply to that cast and thank them for the feedback" }
175
- },
176
- {
177
- name: "assistant",
178
- content: {
179
- text: "I'll reply with a thank you message.",
180
- actions: ["REPLY_TO_CAST"]
181
- }
182
- }
183
- ]
184
- ],
185
- validate: async (runtime, message, state, options) => {
186
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
187
- const __avText = __avTextRaw.toLowerCase();
188
- const __avKeywords = ["reply", "cast"];
189
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
190
- const __avRegex = /\b(?:reply|cast)\b/i;
191
- const __avRegexOk = __avRegex.test(__avText);
192
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
193
- const __avExpectedSource = "farcaster";
194
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
195
- const __avOptions = options && typeof options === "object" ? options : {};
196
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
197
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
198
- return false;
199
- }
200
- const __avLegacyValidate = async (runtime2, message2) => {
201
- const text = message2.content.text?.toLowerCase() || "";
202
- const keywords = ["reply", "respond", "answer", "comment"];
203
- const hasKeyword = keywords.some((keyword) => text.includes(keyword));
204
- const hasParentCast = !!(message2.content.metadata && message2.content.metadata.parentCastHash);
205
- const service = runtime2.getService(FARCASTER_SERVICE_NAME);
206
- const isServiceAvailable = !!service?.getMessageService(runtime2.agentId);
207
- return hasKeyword && (hasParentCast || isServiceAvailable);
208
- };
209
- try {
210
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
211
- } catch {
212
- return false;
213
- }
214
- },
215
- handler: async (runtime, message, state) => {
216
- try {
217
- const service = runtime.getService(FARCASTER_SERVICE_NAME);
218
- const messageService = service?.getMessageService(runtime.agentId);
219
- if (!messageService) {
220
- runtime.logger.error("[REPLY_TO_CAST] MessageService not available");
221
- return { success: false, error: "MessageService not available" };
222
- }
223
- const metadata = message.content.metadata;
224
- const stateParentCastHash = typeof state?.parentCastHash === "string" ? state.parentCastHash : undefined;
225
- const parentCastHash = metadata?.parentCastHash ?? stateParentCastHash;
226
- if (!parentCastHash) {
227
- runtime.logger.error("[REPLY_TO_CAST] No parent cast to reply to");
228
- return { success: false, error: "No parent cast to reply to" };
229
- }
230
- let replyContent = "";
231
- if (state?.replyContent) {
232
- replyContent = state.replyContent;
233
- } else {
234
- const prompt = `Based on this request: "${message.content.text}", generate a helpful and engaging reply for a Farcaster cast (max 320 characters).`;
235
- const response = await runtime.useModel(ModelType.TEXT_LARGE, {
236
- prompt
237
- });
238
- replyContent = typeof response === "string" ? response : String(response);
239
- }
240
- if (replyContent.length > 320) {
241
- replyContent = `${replyContent.substring(0, 317)}...`;
242
- }
243
- const reply = await messageService.sendMessage({
244
- agentId: runtime.agentId,
245
- roomId: message.roomId,
246
- text: replyContent,
247
- type: "REPLY" /* REPLY */,
248
- replyToId: parentCastHash,
249
- metadata: {
250
- parentHash: parentCastHash
251
- }
252
- });
253
- runtime.logger.info(`[REPLY_TO_CAST] Successfully replied to cast: ${reply.id}`);
254
- return { success: true, text: `Replied to cast: ${reply.id}` };
255
- } catch (error) {
256
- runtime.logger.error("[REPLY_TO_CAST] Error replying to cast:", typeof error === "string" ? error : error.message);
257
- throw error;
258
- }
259
- }
260
- };
261
-
262
- // actions/sendCast.ts
263
- import {
264
- createUniqueUuid,
265
- ModelType as ModelType2
266
- } from "@elizaos/core";
267
- var spec2 = requireActionSpec("SEND_CAST");
268
- var sendCastAction = {
269
- name: spec2.name,
270
- similes: spec2.similes ? [...spec2.similes] : [],
271
- description: spec2.description,
272
- examples: spec2.examples ?? [],
273
- validate: async (runtime, message, state, options) => {
274
- const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
275
- const __avText = __avTextRaw.toLowerCase();
276
- const __avKeywords = ["send", "cast"];
277
- const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
278
- const __avRegex = /\b(?:send|cast)\b/i;
279
- const __avRegexOk = __avRegex.test(__avText);
280
- const __avSource = String(message?.content?.source ?? message?.source ?? "");
281
- const __avExpectedSource = "farcaster";
282
- const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
283
- const __avOptions = options && typeof options === "object" ? options : {};
284
- const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
285
- if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
286
- return false;
287
- }
288
- const __avLegacyValidate = async (runtime2, message2) => {
289
- const text = message2.content.text?.toLowerCase() || "";
290
- const keywords = ["post", "cast", "share", "announce", "farcaster"];
291
- const hasKeyword = keywords.some((keyword) => text.includes(keyword));
292
- const service = runtime2.getService(FARCASTER_SERVICE_NAME);
293
- const isServiceAvailable = !!service?.getCastService(runtime2.agentId);
294
- return hasKeyword && isServiceAvailable;
295
- };
296
- try {
297
- return Boolean(await __avLegacyValidate(runtime, message, state, options));
298
- } catch {
299
- return false;
300
- }
301
- },
302
- handler: async (runtime, message, state) => {
303
- try {
304
- const service = runtime.getService(FARCASTER_SERVICE_NAME);
305
- const postService = service?.getCastService(runtime.agentId);
306
- if (!postService) {
307
- runtime.logger.error("[SEND_CAST] PostService not available");
308
- return { success: false, error: "PostService not available" };
309
- }
310
- let castContent = "";
311
- if (state?.castContent) {
312
- castContent = state.castContent;
313
- } else {
314
- 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.`;
315
- const response = await runtime.useModel(ModelType2.TEXT_LARGE, {
316
- prompt
317
- });
318
- castContent = typeof response === "string" ? response : String(response);
319
- }
320
- if (castContent.length > 320) {
321
- castContent = `${castContent.substring(0, 317)}...`;
322
- }
323
- const cast = await postService.createCast({
324
- agentId: runtime.agentId,
325
- roomId: createUniqueUuid(runtime, "farcaster-timeline"),
326
- text: castContent
327
- });
328
- runtime.logger.info(`[SEND_CAST] Successfully posted cast: ${cast.id}`);
329
- await runtime.createMemory({
330
- agentId: runtime.agentId,
331
- roomId: cast.roomId,
332
- entityId: runtime.agentId,
333
- content: {
334
- text: castContent,
335
- source: "farcaster",
336
- metadata: {
337
- castHash: String(cast.metadata?.castHash || ""),
338
- action: "SEND_CAST"
339
- }
340
- },
341
- createdAt: cast.timestamp
342
- }, "messages");
343
- return { success: true, text: `Posted cast: ${cast.id}` };
344
- } catch (error) {
345
- runtime.logger.error("[SEND_CAST] Error posting cast:", typeof error === "string" ? error : error.message);
346
- return { success: false, error: error.message };
347
- }
348
- }
349
- };
350
-
351
- // actions/index.ts
352
- var farcasterActions = [sendCastAction, replyCastAction];
353
-
354
37
  // utils/config.ts
355
- import { parseBooleanFromText } from "@elizaos/core";
356
- import { z as z2 } from "zod";
38
+ var DEFAULT_FARCASTER_ACCOUNT_ID = "default";
357
39
  function getProcessEnv() {
358
40
  if (typeof process === "undefined") {
359
41
  return {};
@@ -367,41 +49,138 @@ function safeParseInt(value, defaultValue) {
367
49
  const parsed = Number.parseInt(value, 10);
368
50
  return Number.isNaN(parsed) ? defaultValue : Math.max(1, parsed);
369
51
  }
370
- function getFarcasterFid(runtime) {
371
- const fidStr = runtime.getSetting("FARCASTER_FID");
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);
372
142
  if (!fidStr)
373
143
  return null;
374
144
  const fid = Number.parseInt(fidStr, 10);
375
145
  return Number.isNaN(fid) ? null : fid;
376
146
  }
377
- function hasFarcasterEnabled(runtime) {
378
- const fid = runtime.getSetting("FARCASTER_FID");
379
- const signerUuid = runtime.getSetting("FARCASTER_SIGNER_UUID");
380
- const apiKey = runtime.getSetting("FARCASTER_NEYNAR_API_KEY");
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);
381
155
  runtime.logger.debug(`[hasFarcasterEnabled] FID: ${fid ? "Found" : "Missing"}`);
382
156
  runtime.logger.debug(`[hasFarcasterEnabled] Signer UUID: ${signerUuid ? "Found" : "Missing"}`);
383
157
  runtime.logger.debug(`[hasFarcasterEnabled] API Key: ${apiKey ? "Found" : "Missing"}`);
384
158
  return !!(fid && signerUuid && apiKey);
385
159
  }
386
- function validateFarcasterConfig(runtime) {
387
- const fid = getFarcasterFid(runtime);
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);
388
167
  try {
389
168
  const farcasterConfig = {
390
- FARCASTER_DRY_RUN: runtime.getSetting("FARCASTER_DRY_RUN") || parseBooleanFromText(env.FARCASTER_DRY_RUN || "false"),
169
+ FARCASTER_DRY_RUN: field(["FARCASTER_DRY_RUN", "dryRun"], "FARCASTER_DRY_RUN") || parseBooleanFromText(env.FARCASTER_DRY_RUN || "false"),
391
170
  FARCASTER_FID: fid ?? undefined,
392
- MAX_CAST_LENGTH: safeParseInt(runtime.getSetting("MAX_CAST_LENGTH"), DEFAULT_MAX_CAST_LENGTH),
393
- FARCASTER_POLL_INTERVAL: safeParseInt(runtime.getSetting("FARCASTER_POLL_INTERVAL"), DEFAULT_POLL_INTERVAL),
394
- ENABLE_CAST: runtime.getSetting("ENABLE_CAST") || parseBooleanFromText(env.ENABLE_CAST || "true"),
395
- CAST_INTERVAL_MIN: safeParseInt(runtime.getSetting("CAST_INTERVAL_MIN"), DEFAULT_CAST_INTERVAL_MIN),
396
- CAST_INTERVAL_MAX: safeParseInt(runtime.getSetting("CAST_INTERVAL_MAX"), DEFAULT_CAST_INTERVAL_MAX),
397
- ENABLE_ACTION_PROCESSING: runtime.getSetting("ENABLE_ACTION_PROCESSING") || parseBooleanFromText(env.ENABLE_ACTION_PROCESSING || "false"),
398
- ACTION_INTERVAL: safeParseInt(runtime.getSetting("ACTION_INTERVAL"), 5),
399
- CAST_IMMEDIATELY: runtime.getSetting("CAST_IMMEDIATELY") || parseBooleanFromText(env.CAST_IMMEDIATELY || "false"),
400
- MAX_ACTIONS_PROCESSING: safeParseInt(runtime.getSetting("MAX_ACTIONS_PROCESSING"), 1),
401
- FARCASTER_SIGNER_UUID: runtime.getSetting("FARCASTER_SIGNER_UUID"),
402
- FARCASTER_NEYNAR_API_KEY: runtime.getSetting("FARCASTER_NEYNAR_API_KEY"),
403
- FARCASTER_HUB_URL: runtime.getSetting("FARCASTER_HUB_URL") || "hub.pinata.cloud",
404
- FARCASTER_MODE: runtime.getSetting("FARCASTER_MODE") || "polling"
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"
405
184
  };
406
185
  runtime.logger.debug(`[validateFarcasterConfig] Resolved FID: ${farcasterConfig.FARCASTER_FID}`);
407
186
  runtime.logger.debug(`[validateFarcasterConfig] Resolved Signer UUID: ${farcasterConfig.FARCASTER_SIGNER_UUID ? "Found" : "Missing"}`);
@@ -421,9 +200,9 @@ function validateFarcasterConfig(runtime) {
421
200
  if (isDryRun) {
422
201
  runtime.logger.info("Farcaster client initialized in dry run mode - no actual casts should be posted");
423
202
  }
424
- return config;
203
+ return { ...config, accountId: normalizedAccountId };
425
204
  } catch (error) {
426
- if (error instanceof z2.ZodError) {
205
+ if (error instanceof z3.ZodError) {
427
206
  const errorMessages = error.issues.map((err) => `${err.path.join(".")}: ${err.message}`).join(`
428
207
  `);
429
208
  throw new Error(`Farcaster configuration validation failed:
@@ -433,32 +212,143 @@ ${errorMessages}`);
433
212
  }
434
213
  }
435
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
+
436
323
  // providers/profileProvider.ts
437
- var spec3 = requireProviderSpec("farcasterProfile");
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
+ }
438
329
  var farcasterProfileProvider = {
439
- name: spec3.name,
330
+ name: spec.name,
440
331
  description: "Provides information about the agent's Farcaster profile",
332
+ descriptionCompressed: "provide information agent Farcaster profile",
441
333
  dynamic: true,
442
- get: async (runtime, _message, _state) => {
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) => {
443
339
  try {
444
340
  const service = runtime.getService(FARCASTER_SERVICE_NAME);
445
- const managers = service?.getActiveManagers();
446
- if (!managers || managers.size === 0) {
341
+ const accountId = readFarcasterAccountId(message, state);
342
+ const manager = service?.getManagerForAccount?.(accountId, runtime.agentId) ?? service?.getManagerForAccount?.(undefined, runtime.agentId);
343
+ if (!manager) {
447
344
  runtime.logger.debug("[FarcasterProfileProvider] No managers available");
448
345
  return {
449
346
  text: "Farcaster profile not available.",
450
347
  data: { available: false }
451
348
  };
452
349
  }
453
- const manager = managers.get(runtime.agentId);
454
- if (!manager) {
455
- runtime.logger.debug("[FarcasterProfileProvider] No manager for this agent");
456
- return {
457
- text: "Farcaster profile not available for this agent.",
458
- data: { available: false }
459
- };
460
- }
461
- const fid = getFarcasterFid(runtime);
350
+ const selectedAccountId = manager.config.accountId;
351
+ const fid = getFarcasterFid(runtime, selectedAccountId);
462
352
  if (!fid) {
463
353
  runtime.logger.warn("[FarcasterProfileProvider] Invalid or missing FARCASTER_FID");
464
354
  return {
@@ -468,18 +358,21 @@ var farcasterProfileProvider = {
468
358
  }
469
359
  try {
470
360
  const profile = await manager.client.getProfile(fid);
361
+ const username = truncateProfileField(profile.username) ?? "";
362
+ const name = truncateProfileField(profile.name);
471
363
  return {
472
- text: `Your Farcaster profile: @${profile.username} (FID: ${profile.fid}). ${profile.name ? `Display name: ${profile.name}` : ""}`,
364
+ text: `Your Farcaster profile: @${username} (FID: ${profile.fid}). ${name ? `Display name: ${name}` : ""}`,
473
365
  data: {
474
366
  available: true,
475
367
  fid: profile.fid,
476
- username: profile.username,
477
- name: profile.name,
478
- pfp: profile.pfp
368
+ username,
369
+ name,
370
+ pfp: profile.pfp,
371
+ accountId: selectedAccountId
479
372
  },
480
373
  values: {
481
374
  fid: profile.fid,
482
- username: profile.username
375
+ username
483
376
  }
484
377
  };
485
378
  } catch (error) {
@@ -499,165 +392,38 @@ var farcasterProfileProvider = {
499
392
  }
500
393
  };
501
394
 
502
- // providers/threadProvider.ts
503
- var formatTimestamp = (timestamp) => {
504
- return new Date(timestamp).toLocaleString("en-US", {
505
- hour: "2-digit",
506
- minute: "2-digit",
507
- month: "short",
508
- day: "numeric"
509
- });
510
- };
511
- var spec4 = requireProviderSpec("farcasterThread");
512
- var farcasterThreadProvider = {
513
- name: spec4.name,
514
- description: "Provides thread context for Farcaster casts so the agent can reference the full conversation.",
515
- dynamic: true,
516
- get: async (runtime, message, _state) => {
517
- const contentForSource = message.content;
518
- const source = typeof contentForSource.source === "string" ? contentForSource.source : undefined;
519
- if (source !== FARCASTER_SOURCE) {
520
- return {
521
- text: "",
522
- data: {
523
- available: false,
524
- reason: `Not a Farcaster message (source=${source ?? "unknown"})`
525
- }
526
- };
527
- }
528
- const service = runtime.getService(FARCASTER_SERVICE_NAME);
529
- const messageService = service?.getMessageService(runtime.agentId);
530
- if (!messageService) {
531
- return {
532
- text: "Farcaster message service not available.",
533
- data: { available: false, error: "service_unavailable" }
534
- };
535
- }
536
- const content = message.content;
537
- const messageMetadata = message.metadata ?? {};
538
- const contentMetadata = typeof content.metadata === "object" && content.metadata !== null && !Array.isArray(content.metadata) ? content.metadata : undefined;
539
- const castHash = content.hash || content.castHash || contentMetadata?.castHash || messageMetadata.castHash || contentMetadata?.parentHash || messageMetadata.parentHash;
540
- if (!castHash || typeof castHash !== "string") {
541
- return {
542
- text: "Unable to resolve Farcaster cast hash for this message.",
543
- data: { available: false, error: "missing_cast_hash" }
544
- };
545
- }
546
- const threadMessages = await messageService.getThread({
547
- agentId: runtime.agentId,
548
- castHash
549
- });
550
- if (!threadMessages || threadMessages.length === 0) {
551
- runtime.logger.debug({ castHash }, "[FarcasterThreadProvider] No thread messages retrieved for cast.");
552
- return {
553
- text: "No Farcaster thread context available.",
554
- data: { available: false, castHash, count: 0 }
555
- };
556
- }
557
- const formattedThread = threadMessages.map((msg, index) => {
558
- const time = formatTimestamp(msg.timestamp);
559
- const username = msg.username || msg.userId || "unknown";
560
- const marker = index === threadMessages.length - 1 ? "→" : "•";
561
- const text = msg.text && msg.text.trim().length > 0 ? msg.text : "<no text>";
562
- return `${marker} [${time}] @${username}: ${text}`;
563
- }).join(`
564
- `);
565
- return {
566
- text: `# Farcaster Thread Context
567
- ${formattedThread}`,
568
- data: {
569
- available: true,
570
- castHash,
571
- count: threadMessages.length
572
- },
573
- values: {
574
- farcasterThread: formattedThread,
575
- farcasterCastHash: castHash,
576
- farcasterCurrentCastText: threadMessages[threadMessages.length - 1]?.text ?? "",
577
- farcasterParentCastText: threadMessages.length > 1 ? threadMessages[threadMessages.length - 2]?.text ?? "" : ""
578
- }
579
- };
580
- }
581
- };
582
-
583
- // providers/timelineProvider.ts
584
- function getTimeAgo(date) {
585
- const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
586
- if (seconds < 60)
587
- return "just now";
588
- if (seconds < 3600)
589
- return `${Math.floor(seconds / 60)}m ago`;
590
- if (seconds < 86400)
591
- return `${Math.floor(seconds / 3600)}h ago`;
592
- return `${Math.floor(seconds / 86400)}d ago`;
593
- }
594
- var spec5 = requireProviderSpec("farcasterTimeline");
595
- var farcasterTimelineProvider = {
596
- name: spec5.name,
597
- description: "Provides recent casts from the agent's Farcaster timeline",
598
- dynamic: true,
599
- get: async (runtime, _message, _state) => {
600
- try {
601
- const service = runtime.getService(FARCASTER_SERVICE_NAME);
602
- const castService = service?.getCastService(runtime.agentId);
603
- if (!castService) {
604
- return {
605
- text: "Farcaster timeline not available.",
606
- data: { available: false }
607
- };
608
- }
609
- const casts = await castService.getCasts({
610
- agentId: runtime.agentId,
611
- limit: 5
612
- });
613
- if (!casts || casts.length === 0) {
614
- return {
615
- text: "No recent casts in your timeline.",
616
- data: {
617
- available: true,
618
- casts: [],
619
- count: 0
620
- }
621
- };
622
- }
623
- const formattedCasts = casts.map((cast, index) => {
624
- const timeAgo = getTimeAgo(new Date(cast.timestamp));
625
- return `${index + 1}. @${cast.username} (${timeAgo}): ${cast.text}`;
626
- }).join(`
627
- `);
628
- return {
629
- text: `Recent casts from your timeline:
630
- ${formattedCasts}`,
631
- data: {
632
- available: true,
633
- castCount: casts.length
634
- },
635
- values: {
636
- latestCastHash: String(casts[0]?.metadata?.castHash || ""),
637
- latestCastText: casts[0]?.text || ""
638
- }
639
- };
640
- } catch (error) {
641
- runtime.logger.error("[FarcasterTimelineProvider] Error:", typeof error === "string" ? error : error.message);
642
- return {
643
- text: "Unable to fetch Farcaster timeline.",
644
- data: {
645
- available: false,
646
- error: error instanceof Error ? error.message : "Unknown error"
647
- }
648
- };
649
- }
650
- }
651
- };
652
-
653
395
  // providers/index.ts
654
- var farcasterProviders = [
655
- farcasterProfileProvider,
656
- farcasterTimelineProvider,
657
- farcasterThreadProvider
658
- ];
396
+ var farcasterProviders = [farcasterProfileProvider];
659
397
 
660
398
  // routes/webhook.ts
399
+ function isRecord(value) {
400
+ return typeof value === "object" && value !== null;
401
+ }
402
+ function isNeynarWebhookData(value) {
403
+ if (!isRecord(value) || typeof value.type !== "string")
404
+ return false;
405
+ if (value.data === undefined)
406
+ return true;
407
+ if (!isRecord(value.data))
408
+ return false;
409
+ if (typeof value.data.hash !== "string")
410
+ return false;
411
+ if (!isRecord(value.data.author) || typeof value.data.author.fid !== "number")
412
+ return false;
413
+ if (value.data.text !== undefined && typeof value.data.text !== "string") {
414
+ return false;
415
+ }
416
+ if (value.data.mentioned_profiles !== undefined && !(Array.isArray(value.data.mentioned_profiles) && value.data.mentioned_profiles.every((profile) => isRecord(profile) && typeof profile.fid === "number"))) {
417
+ return false;
418
+ }
419
+ if (value.data.parent_hash !== undefined && typeof value.data.parent_hash !== "string") {
420
+ return false;
421
+ }
422
+ if (value.data.parent_author !== undefined && !(isRecord(value.data.parent_author) && typeof value.data.parent_author.fid === "number")) {
423
+ return false;
424
+ }
425
+ return true;
426
+ }
661
427
  var farcasterWebhookRoutes = [
662
428
  {
663
429
  type: "POST",
@@ -665,16 +431,25 @@ var farcasterWebhookRoutes = [
665
431
  path: "/webhook",
666
432
  handler: async (req, res, runtime) => {
667
433
  try {
434
+ if (!isNeynarWebhookData(req.body)) {
435
+ res.status(400).json({
436
+ success: false,
437
+ error: "Invalid webhook payload"
438
+ });
439
+ return;
440
+ }
668
441
  const webhookData = req.body;
669
442
  const eventType = webhookData.type;
670
- const farcasterService = runtime?.getService?.("farcaster");
671
- if (farcasterService) {
672
- const agentManager = farcasterService.managers?.get?.(runtime.agentId ?? "");
673
- if (agentManager?.interactions) {
674
- if (agentManager.interactions.mode === "webhook") {
675
- await agentManager.interactions.processWebhookData?.(webhookData);
676
- }
443
+ const farcasterService = runtime?.getService?.(FARCASTER_SERVICE_NAME);
444
+ const accountId = readFarcasterAccountId(webhookData);
445
+ if (farcasterService && accountId) {
446
+ const manager = farcasterService.getManagerForAccount?.(accountId, runtime.agentId);
447
+ if (manager?.interactions.mode === "webhook") {
448
+ await manager.interactions.processWebhookData(webhookData);
677
449
  }
450
+ } else if (farcasterService) {
451
+ const managers = farcasterService.getManagersForAgent?.(runtime.agentId) ?? new Map;
452
+ await Promise.all(Array.from(managers.values()).filter((manager) => manager.interactions.mode === "webhook").map((manager) => manager.interactions.processWebhookData(webhookData)));
678
453
  }
679
454
  res.status(200).json({
680
455
  success: true,
@@ -696,7 +471,10 @@ var farcasterWebhookRoutes = [
696
471
  ];
697
472
 
698
473
  // services/FarcasterService.ts
699
- import { Service } from "@elizaos/core";
474
+ import {
475
+ ChannelType as ChannelType3,
476
+ Service
477
+ } from "@elizaos/core";
700
478
 
701
479
  // managers/AgentManager.ts
702
480
  import { Configuration, NeynarAPIClient } from "@neynar/nodejs-sdk";
@@ -822,7 +600,8 @@ function createCastMemory({
822
600
  roomId,
823
601
  senderId,
824
602
  runtime,
825
- cast
603
+ cast,
604
+ accountId
826
605
  }) {
827
606
  const inReplyTo = cast.inReplyTo ? castUuid({
828
607
  hash: cast.inReplyTo.hash,
@@ -838,17 +617,45 @@ function createCastMemory({
838
617
  content: {
839
618
  text: cast.text,
840
619
  source: FARCASTER_SOURCE,
620
+ ...accountId ? { accountId } : {},
841
621
  url: "",
842
622
  inReplyTo,
843
623
  hash: cast.hash,
844
624
  threadId: cast.threadId,
845
625
  attachments: cast.media && cast.media.length > 0 ? cast.media : undefined
846
626
  },
627
+ metadata: {
628
+ ...accountId ? { accountId } : {}
629
+ },
847
630
  roomId
848
631
  };
849
632
  }
850
633
 
851
634
  // client/FarcasterClient.ts
635
+ async function logNeynarCall(op, context, fn) {
636
+ const startedAt = Date.now();
637
+ elizaLogger.debug({ sdk: "neynar", op, ...context }, `[FarcasterClient] ${op} started`);
638
+ try {
639
+ const result = await fn();
640
+ elizaLogger.info({
641
+ sdk: "neynar",
642
+ op,
643
+ ...context,
644
+ durationMs: Date.now() - startedAt
645
+ }, `[FarcasterClient] ${op} ok`);
646
+ return result;
647
+ } catch (error) {
648
+ const message = error instanceof Error ? error.message : String(error);
649
+ elizaLogger.warn({
650
+ sdk: "neynar",
651
+ op,
652
+ ...context,
653
+ durationMs: Date.now() - startedAt,
654
+ error: message
655
+ }, `[FarcasterClient] ${op} failed`);
656
+ throw error;
657
+ }
658
+ }
852
659
  var castCache = new LRUCache({
853
660
  max: DEFAULT_CAST_CACHE_SIZE,
854
661
  ttl: DEFAULT_CAST_CACHE_TTL
@@ -883,11 +690,11 @@ class FarcasterClient {
883
690
  }
884
691
  async publishCast(cast, parentCastId) {
885
692
  try {
886
- const result = await this.neynar.publishCast({
693
+ const result = await logNeynarCall("publishCast", { textLen: cast.length, parentHash: parentCastId?.hash ?? null }, async () => this.neynar.publishCast({
887
694
  signerUuid: this.signerUuid,
888
695
  text: cast,
889
696
  parent: parentCastId?.hash
890
- });
697
+ }));
891
698
  if (result.success) {
892
699
  return this.getCast(result.cast.hash);
893
700
  }
@@ -897,7 +704,6 @@ class FarcasterClient {
897
704
  elizaLogger.error(`Neynar error: ${JSON.stringify(err.response.data)}`);
898
705
  throw err.response.data;
899
706
  } else {
900
- elizaLogger.error(`Error: ${JSON.stringify(err)}`);
901
707
  throw err;
902
708
  }
903
709
  }
@@ -907,19 +713,19 @@ class FarcasterClient {
907
713
  if (cachedCast) {
908
714
  return cachedCast;
909
715
  }
910
- const response = await this.neynar.lookupCastByHashOrUrl({
716
+ const response = await logNeynarCall("lookupCastByHashOrUrl", { castHash }, async () => this.neynar.lookupCastByHashOrUrl({
911
717
  identifier: castHash,
912
718
  type: "hash"
913
- });
719
+ }));
914
720
  castCache.set(castHash, response.cast);
915
721
  return response.cast;
916
722
  }
917
723
  async getMentions(request) {
918
- const neynarMentionsResponse = await this.neynar.fetchAllNotifications({
724
+ const neynarMentionsResponse = await logNeynarCall("fetchAllNotifications", { fid: request.fid, pageSize: request.pageSize }, async () => this.neynar.fetchAllNotifications({
919
725
  fid: request.fid,
920
726
  type: ["mentions", "replies"],
921
727
  limit: request.pageSize
922
- });
728
+ }));
923
729
  const mentions = [];
924
730
  for (const notification of neynarMentionsResponse.notifications) {
925
731
  const neynarCast = notification.cast;
@@ -933,35 +739,29 @@ class FarcasterClient {
933
739
  if (profileCache.has(fid)) {
934
740
  return profileCache.get(fid);
935
741
  }
936
- try {
937
- const result = await this.neynar.fetchBulkUsers({ fids: [fid] });
938
- if (!result.users || result.users.length < 1) {
939
- elizaLogger.error("Error fetching user by fid");
940
- throw new Error("Profile fetch failed");
941
- }
942
- const neynarUserProfile = result.users[0];
943
- const profile = {
944
- fid,
945
- name: "",
946
- username: ""
947
- };
948
- profile.name = neynarUserProfile.display_name ?? "";
949
- profile.username = neynarUserProfile.username;
950
- profile.bio = neynarUserProfile.profile.bio.text;
951
- profile.pfp = neynarUserProfile.pfp_url;
952
- profileCache.set(fid, profile);
953
- return profile;
954
- } catch (error) {
955
- elizaLogger.error(`Error fetching profile: ${JSON.stringify(error)}`);
956
- throw error;
742
+ const result = await logNeynarCall("fetchBulkUsers", { fid }, async () => this.neynar.fetchBulkUsers({ fids: [fid] }));
743
+ if (result.users.length < 1) {
744
+ throw new Error("Profile fetch failed");
957
745
  }
746
+ const neynarUserProfile = result.users[0];
747
+ const profile = {
748
+ fid,
749
+ name: "",
750
+ username: ""
751
+ };
752
+ profile.name = neynarUserProfile.display_name ?? "";
753
+ profile.username = neynarUserProfile.username;
754
+ profile.bio = neynarUserProfile.profile.bio.text;
755
+ profile.pfp = neynarUserProfile.pfp_url;
756
+ profileCache.set(fid, profile);
757
+ return profile;
958
758
  }
959
759
  async getTimeline(request) {
960
760
  const timeline = [];
961
- const response = await this.neynar.fetchCastsForUser({
761
+ const response = await logNeynarCall("fetchCastsForUser", { fid: request.fid, pageSize: request.pageSize }, async () => this.neynar.fetchCastsForUser({
962
762
  fid: request.fid,
963
763
  limit: request.pageSize
964
- });
764
+ }));
965
765
  for (const cast of response.casts) {
966
766
  castCache.set(cast.hash, cast);
967
767
  timeline.push(neynarCastToCast(cast));
@@ -978,42 +778,45 @@ class FarcasterClient {
978
778
  }
979
779
  async publishReaction(params) {
980
780
  try {
981
- const result = await this.neynar.publishReaction({
781
+ const result = await logNeynarCall("publishReaction", { reactionType: params.reactionType, target: params.target }, async () => this.neynar.publishReaction({
982
782
  signerUuid: this.signerUuid,
983
783
  reactionType: params.reactionType,
984
784
  target: params.target
985
- });
785
+ }));
986
786
  return { success: result.success ?? false };
987
787
  } catch (err) {
988
788
  if (isApiErrorResponse(err)) {
989
789
  elizaLogger.error(`Neynar error publishing reaction: ${JSON.stringify(err.response.data)}`);
990
790
  throw err.response.data;
991
791
  }
992
- elizaLogger.error(`Error publishing reaction: ${JSON.stringify(err)}`);
993
792
  throw err;
994
793
  }
995
794
  }
996
795
  async deleteReaction(params) {
997
796
  try {
998
- const result = await this.neynar.deleteReaction({
797
+ const result = await logNeynarCall("deleteReaction", { reactionType: params.reactionType, target: params.target }, async () => this.neynar.deleteReaction({
999
798
  signerUuid: this.signerUuid,
1000
799
  reactionType: params.reactionType,
1001
800
  target: params.target
1002
- });
801
+ }));
1003
802
  return { success: result.success ?? false };
1004
803
  } catch (err) {
1005
804
  if (isApiErrorResponse(err)) {
1006
805
  elizaLogger.error(`Neynar error deleting reaction: ${JSON.stringify(err.response.data)}`);
1007
806
  throw err.response.data;
1008
807
  }
1009
- elizaLogger.error(`Error deleting reaction: ${JSON.stringify(err)}`);
1010
808
  throw err;
1011
809
  }
1012
810
  }
1013
811
  }
1014
812
 
1015
813
  // managers/CastManager.ts
1016
- import { createUniqueUuid as createUniqueUuid2, EventType } from "@elizaos/core";
814
+ import {
815
+ createUniqueUuid,
816
+ EventType,
817
+ setTrajectoryPurpose,
818
+ withStandaloneTrajectory
819
+ } from "@elizaos/core";
1017
820
 
1018
821
  // utils/callbacks.ts
1019
822
  function standardCastHandlerCallback({
@@ -1027,6 +830,7 @@ function standardCastHandlerCallback({
1027
830
  }) {
1028
831
  const callback = async (content) => {
1029
832
  try {
833
+ const accountId = normalizeFarcasterAccountId(config.accountId ?? DEFAULT_FARCASTER_ACCOUNT_ID);
1030
834
  if (config.FARCASTER_DRY_RUN) {
1031
835
  runtime.logger.info(`[Farcaster] Dry run: would have cast: ${content.text}`);
1032
836
  return [];
@@ -1044,7 +848,8 @@ function standardCastHandlerCallback({
1044
848
  roomId,
1045
849
  senderId: runtime.agentId,
1046
850
  runtime,
1047
- cast: neynarCastToCast(cast)
851
+ cast: neynarCastToCast(cast),
852
+ accountId
1048
853
  });
1049
854
  if (i === 0) {
1050
855
  memory.content.actions = content.actions;
@@ -1081,6 +886,9 @@ class FarcasterCastManager {
1081
886
  this.config = opts.config;
1082
887
  this.fid = this.config.FARCASTER_FID;
1083
888
  }
889
+ getAccountId() {
890
+ return normalizeFarcasterAccountId(this.config.accountId ?? DEFAULT_FARCASTER_ACCOUNT_ID);
891
+ }
1084
892
  async start() {
1085
893
  if (this.isRunning || !this.config.ENABLE_CAST) {
1086
894
  return;
@@ -1123,45 +931,58 @@ class FarcasterCastManager {
1123
931
  }
1124
932
  }
1125
933
  async generateNewCast() {
1126
- this.runtime.logger.info("Generating new cast");
1127
- try {
1128
- const worldId = createUniqueUuid2(this.runtime, this.fid.toString());
1129
- const roomId = createUniqueUuid2(this.runtime, `${this.fid}-home`);
1130
- const callback = standardCastHandlerCallback({
1131
- client: this.client,
1132
- runtime: this.runtime,
1133
- config: this.config,
1134
- roomId,
1135
- onCompletion: async (casts, _memories) => {
1136
- const lastCast = casts[casts.length - 1];
1137
- await this.runtime.setCache(lastCastCacheKey(this.fid), {
1138
- hash: lastCast.hash,
1139
- timestamp: new Date(lastCast.timestamp).getTime()
1140
- });
1141
- }
1142
- });
1143
- await this.runtime.emitEvent(EventType.POST_GENERATED, {
1144
- runtime: this.runtime,
1145
- callback,
1146
- worldId,
1147
- userId: this.runtime.agentId,
1148
- roomId,
1149
- source: FARCASTER_SOURCE
1150
- });
1151
- await this.runtime.emitEvent("FARCASTER_CAST_GENERATED" /* CAST_GENERATED */, {
1152
- runtime: this.runtime,
1153
- source: FARCASTER_SOURCE
1154
- });
1155
- } catch (error) {
1156
- this.runtime.logger.error({ error }, "[Farcaster] Error generating new cast");
1157
- }
934
+ await withStandaloneTrajectory(this.runtime, {
935
+ source: "plugin-farcaster:auto-cast",
936
+ metadata: {
937
+ platform: FARCASTER_SOURCE,
938
+ kind: "public_post_generation",
939
+ fid: this.fid,
940
+ accountId: this.getAccountId()
941
+ }
942
+ }, async () => {
943
+ setTrajectoryPurpose("background");
944
+ this.runtime.logger.info("Generating new cast");
945
+ try {
946
+ const worldId = createUniqueUuid(this.runtime, this.fid.toString());
947
+ const roomId = createUniqueUuid(this.runtime, `${this.fid}-home`);
948
+ const callback = standardCastHandlerCallback({
949
+ client: this.client,
950
+ runtime: this.runtime,
951
+ config: this.config,
952
+ roomId,
953
+ onCompletion: async (casts, _memories) => {
954
+ const lastCast = casts[casts.length - 1];
955
+ await this.runtime.setCache(lastCastCacheKey(this.fid), {
956
+ hash: lastCast.hash,
957
+ timestamp: new Date(lastCast.timestamp).getTime()
958
+ });
959
+ }
960
+ });
961
+ await this.runtime.emitEvent(EventType.POST_GENERATED, {
962
+ runtime: this.runtime,
963
+ callback,
964
+ worldId,
965
+ userId: this.runtime.agentId,
966
+ roomId,
967
+ source: FARCASTER_SOURCE,
968
+ accountId: this.getAccountId()
969
+ });
970
+ await this.runtime.emitEvent("FARCASTER_POST_GENERATED" /* POST_GENERATED */, {
971
+ runtime: this.runtime,
972
+ source: FARCASTER_SOURCE,
973
+ accountId: this.getAccountId()
974
+ });
975
+ } catch (error) {
976
+ this.runtime.logger.error({ error }, "[Farcaster] Error generating new cast");
977
+ }
978
+ });
1158
979
  }
1159
980
  }
1160
981
 
1161
982
  // managers/InteractionManager.ts
1162
983
  import {
1163
984
  ChannelType,
1164
- createUniqueUuid as createUniqueUuid3,
985
+ createUniqueUuid as createUniqueUuid2,
1165
986
  stringToUuid as stringToUuid2
1166
987
  } from "@elizaos/core";
1167
988
 
@@ -1227,12 +1048,12 @@ class AsyncQueue {
1227
1048
  }
1228
1049
 
1229
1050
  // managers/EmbedManager.ts
1230
- import { ModelType as ModelType3 } from "@elizaos/core";
1051
+ import { ModelType, withStandaloneTrajectory as withStandaloneTrajectory2 } from "@elizaos/core";
1231
1052
  function isEmbedUrl(embed) {
1232
- return "url" in embed && typeof embed.url === "string";
1053
+ return "url" in embed;
1233
1054
  }
1234
1055
  function isEmbedCast(embed) {
1235
- return "cast" in embed && typeof embed.cast === "object";
1056
+ return "cast" in embed;
1236
1057
  }
1237
1058
  function getMediaTypeFromUrl(url, contentType) {
1238
1059
  const lowerUrl = url.toLowerCase();
@@ -1326,10 +1147,10 @@ class EmbedManager {
1326
1147
  let description = "An image attachment";
1327
1148
  let title = "Image";
1328
1149
  try {
1329
- const result = await this.runtime.useModel(ModelType3.IMAGE_DESCRIPTION, {
1150
+ const result = await withStandaloneTrajectory2(this.runtime, { source: "farcaster-embed" }, async () => this.runtime.useModel(ModelType.IMAGE_DESCRIPTION, {
1330
1151
  prompt: "Analyze this image and provide a concise title and description. Focus on the main subject and any notable details.",
1331
1152
  imageUrl: url
1332
- });
1153
+ }));
1333
1154
  if (result && typeof result === "object") {
1334
1155
  const typedResult = result;
1335
1156
  description = typedResult.description || description;
@@ -1616,6 +1437,9 @@ class FarcasterInteractionManager {
1616
1437
  });
1617
1438
  this.runtime.logger.info(`Farcaster interaction mode: ${this.mode}`);
1618
1439
  }
1440
+ getAccountId() {
1441
+ return normalizeFarcasterAccountId(this.config.accountId ?? DEFAULT_FARCASTER_ACCOUNT_ID);
1442
+ }
1619
1443
  async processMention(cast) {
1620
1444
  const agentFid = this.config.FARCASTER_FID;
1621
1445
  const agent = await this.client.getProfile(agentFid);
@@ -1710,9 +1534,9 @@ class FarcasterInteractionManager {
1710
1534
  hash: cast.hash
1711
1535
  });
1712
1536
  const conversationId = cast.threadId ?? cast.inReplyTo?.hash ?? cast.hash;
1713
- const entityId = createUniqueUuid3(this.runtime, cast.authorFid.toString());
1714
- const worldId = createUniqueUuid3(this.runtime, cast.authorFid.toString());
1715
- const roomId = createUniqueUuid3(this.runtime, conversationId);
1537
+ const entityId = createUniqueUuid2(this.runtime, cast.authorFid.toString());
1538
+ const worldId = createUniqueUuid2(this.runtime, cast.authorFid.toString());
1539
+ const roomId = createUniqueUuid2(this.runtime, conversationId);
1716
1540
  if (entityId !== this.runtime.agentId) {
1717
1541
  await this.runtime.ensureConnection({
1718
1542
  entityId,
@@ -1726,8 +1550,10 @@ class FarcasterInteractionManager {
1726
1550
  messageServerId: stringToUuid2(cast.authorFid.toString()),
1727
1551
  worldId,
1728
1552
  metadata: {
1553
+ accountId: this.getAccountId(),
1729
1554
  ownership: { ownerId: cast.authorFid.toString() },
1730
1555
  farcaster: {
1556
+ accountId: this.getAccountId(),
1731
1557
  username: cast.profile.username,
1732
1558
  id: cast.authorFid.toString(),
1733
1559
  name: cast.profile.name
@@ -1752,12 +1578,16 @@ class FarcasterInteractionManager {
1752
1578
  hash: cast.inReplyTo.hash
1753
1579
  }) : undefined,
1754
1580
  source: FARCASTER_SOURCE,
1581
+ accountId: this.getAccountId(),
1755
1582
  channelType: ChannelType.THREAD,
1756
1583
  attachments: cast.media && cast.media.length > 0 ? cast.media : undefined
1757
1584
  },
1758
1585
  entityId,
1759
1586
  roomId,
1760
- createdAt: cast.timestamp.getTime()
1587
+ createdAt: cast.timestamp.getTime(),
1588
+ metadata: {
1589
+ accountId: this.getAccountId()
1590
+ }
1761
1591
  };
1762
1592
  return memory;
1763
1593
  });
@@ -1786,7 +1616,8 @@ class FarcasterInteractionManager {
1786
1616
  runtime,
1787
1617
  memory: newMemory,
1788
1618
  cast: currentCast,
1789
- source: FARCASTER_SOURCE
1619
+ source: FARCASTER_SOURCE,
1620
+ accountId: self.getAccountId()
1790
1621
  });
1791
1622
  }
1792
1623
  thread.unshift(currentCast);
@@ -1840,6 +1671,7 @@ class FarcasterInteractionManager {
1840
1671
  memory,
1841
1672
  cast,
1842
1673
  source: FARCASTER_SOURCE,
1674
+ accountId: this.getAccountId(),
1843
1675
  callback
1844
1676
  };
1845
1677
  this.runtime.emitEvent("FARCASTER_MENTION_RECEIVED" /* MENTION_RECEIVED */, mentionPayload);
@@ -1860,8 +1692,10 @@ class FarcasterAgentManager {
1860
1692
  client;
1861
1693
  casts;
1862
1694
  interactions;
1695
+ config;
1863
1696
  constructor(runtime, config) {
1864
1697
  this.runtime = runtime;
1698
+ this.config = config;
1865
1699
  const signerUuid = config.FARCASTER_SIGNER_UUID;
1866
1700
  const neynarConfig = new Configuration({
1867
1701
  apiKey: config.FARCASTER_NEYNAR_API_KEY
@@ -1886,18 +1720,44 @@ class FarcasterAgentManager {
1886
1720
  }
1887
1721
 
1888
1722
  // services/CastService.ts
1889
- import { createUniqueUuid as createUniqueUuid4, ModelType as ModelType4 } from "@elizaos/core";
1723
+ import {
1724
+ ChannelType as ChannelType2,
1725
+ createUniqueUuid as createUniqueUuid3,
1726
+ ModelType as ModelType2
1727
+ } from "@elizaos/core";
1728
+ function clampLimit(value, fallback, max) {
1729
+ if (!Number.isFinite(value)) {
1730
+ return fallback;
1731
+ }
1732
+ return Math.min(Math.max(1, Math.floor(value)), max);
1733
+ }
1734
+ function readContentString(content, keys) {
1735
+ const record = content;
1736
+ for (const key of keys) {
1737
+ const value = record[key];
1738
+ if (typeof value === "string" && value.trim()) {
1739
+ return value.trim();
1740
+ }
1741
+ }
1742
+ return;
1743
+ }
1744
+
1890
1745
  class FarcasterCastService {
1891
1746
  client;
1892
1747
  runtime;
1748
+ accountId;
1893
1749
  static serviceType = "ICastService";
1894
- constructor(client, runtime) {
1750
+ constructor(client, runtime, accountId = DEFAULT_FARCASTER_ACCOUNT_ID) {
1895
1751
  this.client = client;
1896
1752
  this.runtime = runtime;
1753
+ this.accountId = accountId;
1754
+ }
1755
+ getAccountId() {
1756
+ return normalizeFarcasterAccountId(this.accountId);
1897
1757
  }
1898
1758
  async getCasts(params) {
1899
1759
  try {
1900
- const fid = getFarcasterFid(this.runtime);
1760
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
1901
1761
  if (!fid) {
1902
1762
  this.runtime.logger.error("FARCASTER_FID is not configured");
1903
1763
  return [];
@@ -1940,6 +1800,7 @@ class FarcasterCastService {
1940
1800
  inReplyTo: params.replyTo?.hash,
1941
1801
  media: [],
1942
1802
  metadata: {
1803
+ accountId: this.getAccountId(),
1943
1804
  castHash: cast.hash,
1944
1805
  authorFid: cast.authorFid,
1945
1806
  source: FARCASTER_SOURCE,
@@ -1953,6 +1814,58 @@ class FarcasterCastService {
1953
1814
  throw error;
1954
1815
  }
1955
1816
  }
1817
+ async handleSendPost(runtime, content) {
1818
+ const requestedAccountId = normalizeFarcasterAccountId(readFarcasterAccountId(content) ?? this.getAccountId());
1819
+ if (requestedAccountId !== this.getAccountId()) {
1820
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
1821
+ }
1822
+ const text = typeof content.text === "string" ? content.text.trim() : "";
1823
+ if (!text) {
1824
+ throw new Error("Farcaster post connector requires non-empty text content.");
1825
+ }
1826
+ const parentHash = readContentString(content, ["parentHash", "replyTo", "replyToHash"]);
1827
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
1828
+ const cast = await this.createCast({
1829
+ agentId: runtime.agentId,
1830
+ roomId: createUniqueUuid3(runtime, `farcaster:feed:${fid ?? runtime.agentId}`),
1831
+ text,
1832
+ ...parentHash && fid ? { replyTo: { hash: parentHash, fid } } : {}
1833
+ });
1834
+ return this.farcasterCastToMemory(runtime, cast);
1835
+ }
1836
+ async fetchFeed(context, params = {}) {
1837
+ const requestedAccountId = normalizeFarcasterAccountId(context.accountId ?? context.metadata?.accountId ?? this.getAccountId());
1838
+ if (requestedAccountId !== this.getAccountId()) {
1839
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
1840
+ }
1841
+ const casts = await this.getCasts({
1842
+ agentId: context.runtime.agentId,
1843
+ limit: clampLimit(params.limit, 25, 100),
1844
+ cursor: params.cursor
1845
+ });
1846
+ return casts.map((cast) => this.farcasterCastToMemory(context.runtime, cast));
1847
+ }
1848
+ async searchPosts(context, params) {
1849
+ const requestedAccountId = normalizeFarcasterAccountId(context.accountId ?? context.metadata?.accountId ?? this.getAccountId());
1850
+ if (requestedAccountId !== this.getAccountId()) {
1851
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
1852
+ }
1853
+ const query = params.query.trim().toLowerCase();
1854
+ if (!query) {
1855
+ return [];
1856
+ }
1857
+ const limit = clampLimit(params.limit, 25, 100);
1858
+ const casts = await this.getCasts({
1859
+ agentId: context.runtime.agentId,
1860
+ limit: 100,
1861
+ cursor: params.cursor
1862
+ });
1863
+ return casts.filter((cast) => {
1864
+ const text = cast.text.toLowerCase();
1865
+ const username = cast.username.toLowerCase();
1866
+ return text.includes(query) || username.includes(query);
1867
+ }).slice(0, limit).map((cast) => this.farcasterCastToMemory(context.runtime, cast));
1868
+ }
1956
1869
  async deleteCast(params) {
1957
1870
  this.runtime.logger.warn(`Cast deletion is not supported by the Farcaster API: ${JSON.stringify({ castHash: params.castHash })}`);
1958
1871
  }
@@ -2018,7 +1931,7 @@ class FarcasterCastService {
2018
1931
  }
2019
1932
  async getMentions(params) {
2020
1933
  try {
2021
- const fid = getFarcasterFid(this.runtime);
1934
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
2022
1935
  if (!fid) {
2023
1936
  this.runtime.logger.error("FARCASTER_FID is not configured");
2024
1937
  return [];
@@ -2039,7 +1952,7 @@ class FarcasterCastService {
2039
1952
  async generateCastContent() {
2040
1953
  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.`;
2041
1954
  try {
2042
- const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
1955
+ const response = await this.runtime.useModel(ModelType2.TEXT_SMALL, {
2043
1956
  prompt,
2044
1957
  maxTokens: 100
2045
1958
  });
@@ -2052,7 +1965,7 @@ class FarcasterCastService {
2052
1965
  async truncateCast(text) {
2053
1966
  const prompt = `Shorten this text to under 320 characters while keeping the main message intact: "${text}"`;
2054
1967
  try {
2055
- const response = await this.runtime.useModel(ModelType4.TEXT_SMALL, {
1968
+ const response = await this.runtime.useModel(ModelType2.TEXT_SMALL, {
2056
1969
  prompt,
2057
1970
  maxTokens: 100
2058
1971
  });
@@ -2068,9 +1981,9 @@ class FarcasterCastService {
2068
1981
  }
2069
1982
  async storeCastInMemory(roomId, cast) {
2070
1983
  try {
2071
- const entityId = createUniqueUuid4(this.runtime, cast.userId);
1984
+ const entityId = createUniqueUuid3(this.runtime, cast.userId);
2072
1985
  const memory = {
2073
- id: createUniqueUuid4(this.runtime, cast.id),
1986
+ id: createUniqueUuid3(this.runtime, cast.id),
2074
1987
  agentId: this.runtime.agentId,
2075
1988
  entityId,
2076
1989
  content: {
@@ -2078,7 +1991,11 @@ class FarcasterCastService {
2078
1991
  castHash: String(cast.metadata?.castHash || ""),
2079
1992
  castId: cast.id,
2080
1993
  author: cast.username,
2081
- timestamp: cast.timestamp
1994
+ timestamp: cast.timestamp,
1995
+ accountId: this.getAccountId()
1996
+ },
1997
+ metadata: {
1998
+ accountId: this.getAccountId()
2082
1999
  },
2083
2000
  roomId,
2084
2001
  createdAt: Date.now()
@@ -2088,11 +2005,58 @@ class FarcasterCastService {
2088
2005
  this.runtime.logger.error(`Failed to store cast in memory: ${JSON.stringify({ error })}`);
2089
2006
  }
2090
2007
  }
2008
+ farcasterCastToMemory(runtime, cast) {
2009
+ const authorId = cast.userId || "unknown";
2010
+ const entityId = authorId === runtime.agentId ? runtime.agentId : createUniqueUuid3(runtime, `farcaster:user:${authorId}`);
2011
+ const roomId = cast.roomId || createUniqueUuid3(runtime, `farcaster:feed:${authorId}`);
2012
+ const castHash = typeof cast.metadata?.castHash === "string" ? cast.metadata.castHash : cast.id;
2013
+ return {
2014
+ id: createUniqueUuid3(runtime, `farcaster:cast:${castHash}`),
2015
+ agentId: runtime.agentId,
2016
+ entityId,
2017
+ roomId,
2018
+ createdAt: cast.timestamp || Date.now(),
2019
+ content: {
2020
+ text: cast.text,
2021
+ source: FARCASTER_SOURCE,
2022
+ channelType: ChannelType2.FEED,
2023
+ ...cast.inReplyTo ? { inReplyTo: createUniqueUuid3(runtime, `farcaster:cast:${cast.inReplyTo}`) } : {}
2024
+ },
2025
+ metadata: {
2026
+ type: "message",
2027
+ source: FARCASTER_SOURCE,
2028
+ accountId: this.getAccountId(),
2029
+ provider: FARCASTER_SOURCE,
2030
+ timestamp: cast.timestamp,
2031
+ fromBot: entityId === runtime.agentId,
2032
+ messageIdFull: castHash,
2033
+ chatType: ChannelType2.FEED,
2034
+ sender: {
2035
+ id: authorId,
2036
+ username: cast.username
2037
+ },
2038
+ farcaster: {
2039
+ accountId: this.getAccountId(),
2040
+ castId: cast.id,
2041
+ castHash,
2042
+ authorFid: cast.metadata?.authorFid,
2043
+ username: cast.username,
2044
+ inReplyTo: cast.inReplyTo,
2045
+ metrics: {
2046
+ recasts: cast.metadata?.recasts,
2047
+ replies: cast.metadata?.replies,
2048
+ likes: cast.metadata?.likes
2049
+ },
2050
+ ...cast.metadata ?? {}
2051
+ }
2052
+ }
2053
+ };
2054
+ }
2091
2055
  castToFarcasterCast(cast, agentId) {
2092
2056
  return {
2093
2057
  id: castUuid({ hash: cast.hash, agentId }),
2094
2058
  agentId,
2095
- roomId: createUniqueUuid4(this.runtime, cast.threadId || cast.hash),
2059
+ roomId: createUniqueUuid3(this.runtime, cast.threadId || cast.hash),
2096
2060
  userId: cast.profile.fid.toString(),
2097
2061
  username: cast.profile.username,
2098
2062
  text: cast.text,
@@ -2102,6 +2066,7 @@ class FarcasterCastService {
2102
2066
  castHash: cast.hash,
2103
2067
  authorFid: cast.authorFid,
2104
2068
  source: FARCASTER_SOURCE,
2069
+ accountId: this.getAccountId(),
2105
2070
  ...cast.threadId ? { threadId: cast.threadId } : {},
2106
2071
  ...cast.stats ? {
2107
2072
  recasts: cast.stats.recasts,
@@ -2114,19 +2079,24 @@ class FarcasterCastService {
2114
2079
  }
2115
2080
 
2116
2081
  // services/MessageService.ts
2117
- import { createUniqueUuid as createUniqueUuid5 } from "@elizaos/core";
2082
+ import { createUniqueUuid as createUniqueUuid4 } from "@elizaos/core";
2118
2083
  class FarcasterMessageService {
2119
2084
  client;
2120
2085
  runtime;
2121
- constructor(client, runtime) {
2086
+ accountId;
2087
+ constructor(client, runtime, accountId = DEFAULT_FARCASTER_ACCOUNT_ID) {
2122
2088
  this.client = client;
2123
2089
  this.runtime = runtime;
2090
+ this.accountId = accountId;
2091
+ }
2092
+ getAccountId() {
2093
+ return normalizeFarcasterAccountId(this.accountId);
2124
2094
  }
2125
2095
  castToMessage(cast, agentId, extraMetadata) {
2126
2096
  return {
2127
2097
  id: castUuid({ hash: cast.hash, agentId }),
2128
2098
  agentId,
2129
- roomId: createUniqueUuid5(this.runtime, cast.threadId || cast.hash),
2099
+ roomId: createUniqueUuid4(this.runtime, cast.threadId || cast.hash),
2130
2100
  userId: cast.profile.fid.toString(),
2131
2101
  username: cast.profile.username,
2132
2102
  text: cast.text,
@@ -2135,6 +2105,7 @@ class FarcasterMessageService {
2135
2105
  inReplyTo: cast.inReplyTo ? castUuid({ hash: cast.inReplyTo.hash, agentId }) : undefined,
2136
2106
  metadata: {
2137
2107
  source: FARCASTER_SOURCE,
2108
+ accountId: this.getAccountId(),
2138
2109
  castHash: cast.hash,
2139
2110
  threadId: cast.threadId,
2140
2111
  authorFid: cast.authorFid,
@@ -2145,7 +2116,7 @@ class FarcasterMessageService {
2145
2116
  async getMessages(options) {
2146
2117
  try {
2147
2118
  const { agentId, roomId, limit = 20 } = options;
2148
- const fid = getFarcasterFid(this.runtime);
2119
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
2149
2120
  if (!fid) {
2150
2121
  this.runtime.logger.error("[Farcaster] FARCASTER_FID is not configured");
2151
2122
  return [];
@@ -2169,11 +2140,15 @@ class FarcasterMessageService {
2169
2140
  }
2170
2141
  async sendMessage(options) {
2171
2142
  try {
2143
+ const requestedAccountId = normalizeFarcasterAccountId(options.accountId ?? readFarcasterAccountId(options.metadata) ?? this.getAccountId());
2144
+ if (requestedAccountId !== this.getAccountId()) {
2145
+ throw new Error(`Farcaster account '${requestedAccountId}' is not available in this service instance`);
2146
+ }
2172
2147
  const { text, type, roomId, replyToId, agentId } = options;
2173
2148
  let inReplyTo;
2174
2149
  if (replyToId && type === "REPLY" /* REPLY */) {
2175
2150
  const parentHash = typeof options.metadata?.parentHash === "string" ? options.metadata.parentHash : replyToId;
2176
- const fid = getFarcasterFid(this.runtime);
2151
+ const fid = getFarcasterFid(this.runtime, this.getAccountId());
2177
2152
  if (!fid) {
2178
2153
  throw new Error("FARCASTER_FID is not configured");
2179
2154
  }
@@ -2193,15 +2168,16 @@ class FarcasterMessageService {
2193
2168
  const message = this.castToMessage(cast, agentId, options.metadata);
2194
2169
  message.roomId = roomId;
2195
2170
  message.type = type;
2196
- const castGeneratedPayload = {
2171
+ const postGeneratedPayload = {
2197
2172
  runtime: this.runtime,
2198
2173
  source: FARCASTER_SOURCE,
2199
2174
  castHash: cast.hash,
2200
2175
  ...cast.threadId ? { threadId: cast.threadId } : {},
2201
2176
  messageId: message.id,
2202
- roomId
2177
+ roomId,
2178
+ accountId: this.getAccountId()
2203
2179
  };
2204
- await this.runtime.emitEvent("FARCASTER_CAST_GENERATED" /* CAST_GENERATED */, castGeneratedPayload);
2180
+ await this.runtime.emitEvent("FARCASTER_POST_GENERATED" /* POST_GENERATED */, postGeneratedPayload);
2205
2181
  return message;
2206
2182
  } catch (error) {
2207
2183
  const err = error instanceof Error ? error : new Error(String(error));
@@ -2253,9 +2229,7 @@ class FarcasterMessageService {
2253
2229
  // services/FarcasterService.ts
2254
2230
  class FarcasterService extends Service {
2255
2231
  static instance;
2256
- managers = new Map;
2257
- messageServices = new Map;
2258
- castServices = new Map;
2232
+ agents = new Map;
2259
2233
  static serviceType = FARCASTER_SERVICE_NAME;
2260
2234
  description = "Farcaster integration service for sending and receiving casts";
2261
2235
  capabilityDescription = "The agent is able to send and receive messages on farcaster";
@@ -2270,8 +2244,7 @@ class FarcasterService extends Service {
2270
2244
  }
2271
2245
  static async start(runtime) {
2272
2246
  const service = FarcasterService.getInstance();
2273
- let manager = service.managers.get(runtime.agentId);
2274
- if (manager) {
2247
+ if (service.agents.has(runtime.agentId)) {
2275
2248
  runtime.logger.warn({ agentId: runtime.agentId }, "Farcaster service already started");
2276
2249
  return service;
2277
2250
  }
@@ -2279,80 +2252,213 @@ class FarcasterService extends Service {
2279
2252
  runtime.logger.debug({ agentId: runtime.agentId }, "Farcaster service not enabled");
2280
2253
  return service;
2281
2254
  }
2282
- const farcasterConfig = validateFarcasterConfig(runtime);
2283
- manager = new FarcasterAgentManager(runtime, farcasterConfig);
2284
- service.managers.set(runtime.agentId, manager);
2285
- const messageService = new FarcasterMessageService(manager.client, runtime);
2286
- const castService = new FarcasterCastService(manager.client, runtime);
2287
- service.messageServices.set(runtime.agentId, messageService);
2288
- service.castServices.set(runtime.agentId, castService);
2289
- await manager.start();
2290
- runtime.logger.success({ agentId: runtime.agentId }, "Farcaster client started");
2255
+ const accounts = {
2256
+ defaultAccountId: normalizeFarcasterAccountId(resolveDefaultFarcasterAccountId(runtime)),
2257
+ managers: new Map,
2258
+ messageServices: new Map,
2259
+ castServices: new Map
2260
+ };
2261
+ service.agents.set(runtime.agentId, accounts);
2262
+ for (const accountId of listFarcasterAccountIds(runtime)) {
2263
+ if (!hasFarcasterEnabled(runtime, accountId)) {
2264
+ continue;
2265
+ }
2266
+ const farcasterConfig = validateFarcasterConfig(runtime, accountId);
2267
+ const manager = new FarcasterAgentManager(runtime, farcasterConfig);
2268
+ accounts.managers.set(accountId, manager);
2269
+ accounts.messageServices.set(accountId, new FarcasterMessageService(manager.client, runtime, accountId));
2270
+ accounts.castServices.set(accountId, new FarcasterCastService(manager.client, runtime, accountId));
2271
+ await manager.start();
2272
+ runtime.logger.success({ agentId: runtime.agentId, accountId }, "Farcaster client started");
2273
+ }
2291
2274
  return service;
2292
2275
  }
2293
2276
  static async stop(runtime) {
2294
2277
  const service = FarcasterService.getInstance();
2295
- const manager = service.managers.get(runtime.agentId);
2296
- if (manager) {
2297
- await manager.stop();
2298
- service.managers.delete(runtime.agentId);
2299
- service.messageServices.delete(runtime.agentId);
2300
- service.castServices.delete(runtime.agentId);
2278
+ const accounts = service.agents.get(runtime.agentId);
2279
+ if (accounts) {
2280
+ for (const manager of accounts.managers.values()) {
2281
+ await manager.stop();
2282
+ }
2283
+ service.agents.delete(runtime.agentId);
2301
2284
  runtime.logger.info({ agentId: runtime.agentId }, "Farcaster client stopped");
2302
2285
  } else {
2303
2286
  runtime.logger.debug({ agentId: runtime.agentId }, "Farcaster service not running");
2304
2287
  }
2305
2288
  }
2289
+ static registerSendHandlers(runtime, serviceInstance) {
2290
+ const accounts = serviceInstance?.agents.get(runtime.agentId);
2291
+ if (!accounts || accounts.castServices.size === 0) {
2292
+ runtime.logger.warn({ src: "plugin:farcaster", agentId: runtime.agentId }, "Cannot register Farcaster post connector; cast service is not initialized");
2293
+ return;
2294
+ }
2295
+ const withPostConnector = runtime;
2296
+ if (typeof withPostConnector.registerPostConnector !== "function") {
2297
+ return;
2298
+ }
2299
+ for (const castService of accounts.castServices.values()) {
2300
+ const accountId = castService.getAccountId();
2301
+ withPostConnector.registerPostConnector({
2302
+ source: "farcaster",
2303
+ accountId,
2304
+ label: "Farcaster",
2305
+ description: "Farcaster public cast connector for publishing casts and reading or searching the authenticated account's recent feed.",
2306
+ capabilities: ["post", "fetch_feed", "search_posts"],
2307
+ contexts: ["social", "social_posting", "connectors"],
2308
+ metadata: {
2309
+ accountId,
2310
+ service: FARCASTER_SERVICE_NAME
2311
+ },
2312
+ postHandler: castService.handleSendPost.bind(castService),
2313
+ fetchFeed: castService.fetchFeed.bind(castService),
2314
+ searchPosts: castService.searchPosts.bind(castService),
2315
+ contentShaping: {
2316
+ systemPromptFragment: "For Farcaster casts, write a conversational public cast under 320 characters. If replying, keep enough context for a public thread.",
2317
+ constraints: {
2318
+ maxLength: 320,
2319
+ supportsMarkdown: false,
2320
+ channelType: ChannelType3.FEED
2321
+ }
2322
+ }
2323
+ });
2324
+ }
2325
+ runtime.logger.info({ src: "plugin:farcaster", agentId: runtime.agentId }, "Registered Farcaster post connector");
2326
+ }
2306
2327
  async stop() {
2307
- for (const manager of Array.from(this.managers.values())) {
2308
- const agentId = manager.runtime.agentId;
2309
- manager.runtime.logger.debug("Stopping Farcaster service");
2328
+ for (const [agentId, accounts] of Array.from(this.agents.entries())) {
2329
+ const runtime = accounts.managers.values().next().value?.runtime;
2330
+ runtime?.logger.debug("Stopping Farcaster service");
2310
2331
  try {
2311
- await FarcasterService.stop(manager.runtime);
2332
+ if (runtime) {
2333
+ await FarcasterService.stop(runtime);
2334
+ } else {
2335
+ this.agents.delete(agentId);
2336
+ }
2312
2337
  } catch (error) {
2313
- manager.runtime.logger.error({ agentId, error }, "Error stopping Farcaster service");
2338
+ runtime?.logger.error({ agentId, error }, "Error stopping Farcaster service");
2314
2339
  }
2315
2340
  }
2316
2341
  }
2317
- getMessageService(agentId) {
2318
- return this.messageServices.get(agentId);
2342
+ getMessageService(agentId, accountId) {
2343
+ return this.getMessageServiceForAccount(accountId, agentId);
2319
2344
  }
2320
- getCastService(agentId) {
2321
- return this.castServices.get(agentId);
2345
+ getCastService(agentId, accountId) {
2346
+ return this.getCastServiceForAccount(accountId, agentId);
2347
+ }
2348
+ getMessageServiceForAccount(accountId, agentId) {
2349
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2350
+ if (!resolvedAgentId)
2351
+ return;
2352
+ const accounts = this.agents.get(resolvedAgentId);
2353
+ if (!accounts)
2354
+ return;
2355
+ const id = accountId ? normalizeFarcasterAccountId(accountId) : accounts.defaultAccountId;
2356
+ return accounts.messageServices.get(id);
2357
+ }
2358
+ getCastServiceForAccount(accountId, agentId) {
2359
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2360
+ if (!resolvedAgentId)
2361
+ return;
2362
+ const accounts = this.agents.get(resolvedAgentId);
2363
+ if (!accounts)
2364
+ return;
2365
+ const id = accountId ? normalizeFarcasterAccountId(accountId) : accounts.defaultAccountId;
2366
+ return accounts.castServices.get(id);
2367
+ }
2368
+ getManagerForAccount(accountId, agentId) {
2369
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2370
+ if (!resolvedAgentId)
2371
+ return;
2372
+ const accounts = this.agents.get(resolvedAgentId);
2373
+ if (!accounts)
2374
+ return;
2375
+ const id = accountId ? normalizeFarcasterAccountId(accountId) : accounts.defaultAccountId;
2376
+ return accounts.managers.get(id);
2377
+ }
2378
+ getDefaultAccountId(agentId) {
2379
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2380
+ return resolvedAgentId ? this.agents.get(resolvedAgentId)?.defaultAccountId : undefined;
2381
+ }
2382
+ listAccountIds(agentId) {
2383
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2384
+ const accounts = resolvedAgentId ? this.agents.get(resolvedAgentId) : undefined;
2385
+ return accounts ? Array.from(accounts.managers.keys()) : [];
2386
+ }
2387
+ getManagersForAgent(agentId) {
2388
+ const resolvedAgentId = agentId ?? this.firstAgentId();
2389
+ const accounts = resolvedAgentId ? this.agents.get(resolvedAgentId) : undefined;
2390
+ return new Map(accounts?.managers ?? []);
2322
2391
  }
2323
2392
  async healthCheck() {
2324
2393
  const managerStatuses = {};
2325
2394
  let overallHealthy = true;
2326
- for (const [agentId, manager] of Array.from(this.managers.entries())) {
2327
- try {
2328
- const fid = getFarcasterFid(manager.runtime);
2329
- if (!fid) {
2330
- throw new Error("FARCASTER_FID not configured");
2395
+ for (const [agentId, accounts] of Array.from(this.agents.entries())) {
2396
+ managerStatuses[agentId] = {};
2397
+ for (const [accountId, manager] of Array.from(accounts.managers.entries())) {
2398
+ try {
2399
+ const fid = getFarcasterFid(manager.runtime, accountId);
2400
+ if (!fid) {
2401
+ throw new Error("FARCASTER_FID not configured");
2402
+ }
2403
+ const profile = await manager.client.getProfile(fid);
2404
+ managerStatuses[agentId][accountId] = {
2405
+ status: "healthy",
2406
+ fid: profile.fid,
2407
+ username: profile.username
2408
+ };
2409
+ } catch (error) {
2410
+ managerStatuses[agentId][accountId] = {
2411
+ status: "unhealthy",
2412
+ error: error instanceof Error ? error.message : "Unknown error"
2413
+ };
2414
+ overallHealthy = false;
2331
2415
  }
2332
- const profile = await manager.client.getProfile(fid);
2333
- managerStatuses[agentId] = {
2334
- status: "healthy",
2335
- fid: profile.fid,
2336
- username: profile.username
2337
- };
2338
- } catch (error) {
2339
- managerStatuses[agentId] = {
2340
- status: "unhealthy",
2341
- error: error instanceof Error ? error.message : "Unknown error"
2342
- };
2343
- overallHealthy = false;
2344
2416
  }
2345
2417
  }
2346
2418
  return {
2347
2419
  healthy: overallHealthy,
2348
2420
  details: {
2349
- activeManagers: this.managers.size,
2421
+ activeManagers: Array.from(this.agents.values()).reduce((total, accounts) => total + accounts.managers.size, 0),
2350
2422
  managerStatuses
2351
2423
  }
2352
2424
  };
2353
2425
  }
2354
2426
  getActiveManagers() {
2355
- return new Map(this.managers);
2427
+ return new Map(Array.from(this.agents.entries()).flatMap(([agentId, accounts]) => Array.from(accounts.managers.entries()).map(([accountId, manager]) => [`${agentId}:${accountId}`, manager])));
2428
+ }
2429
+ firstAgentId() {
2430
+ return this.agents.keys().next().value;
2431
+ }
2432
+ }
2433
+
2434
+ // workflow-credential-provider.ts
2435
+ import { Service as Service2 } from "@elizaos/core";
2436
+ var WORKFLOW_CREDENTIAL_PROVIDER_TYPE = "workflow_credential_provider";
2437
+ var SUPPORTED = ["httpHeaderAuth"];
2438
+
2439
+ class FarcasterWorkflowCredentialProvider extends Service2 {
2440
+ static serviceType = WORKFLOW_CREDENTIAL_PROVIDER_TYPE;
2441
+ capabilityDescription = "Supplies Farcaster (Neynar API) credentials to the workflow plugin.";
2442
+ static async start(runtime) {
2443
+ return new FarcasterWorkflowCredentialProvider(runtime);
2444
+ }
2445
+ async stop() {}
2446
+ async resolve(_userId, credType) {
2447
+ if (credType !== "httpHeaderAuth")
2448
+ return null;
2449
+ const neynarApiKey = this.runtime.getSetting("FARCASTER_NEYNAR_API_KEY");
2450
+ if (!neynarApiKey?.trim())
2451
+ return null;
2452
+ return {
2453
+ status: "credential_data",
2454
+ data: { name: "api_key", value: neynarApiKey.trim() }
2455
+ };
2456
+ }
2457
+ checkCredentialTypes(credTypes) {
2458
+ return {
2459
+ supported: credTypes.filter((t) => SUPPORTED.includes(t)),
2460
+ unsupported: credTypes.filter((t) => !SUPPORTED.includes(t))
2461
+ };
2356
2462
  }
2357
2463
  }
2358
2464
 
@@ -2360,20 +2466,40 @@ class FarcasterService extends Service {
2360
2466
  var farcasterPlugin = {
2361
2467
  name: "farcaster",
2362
2468
  description: "Farcaster client plugin for sending and receiving casts",
2363
- services: [FarcasterService],
2364
- actions: farcasterActions,
2469
+ services: [FarcasterService, FarcasterWorkflowCredentialProvider],
2470
+ actions: [],
2365
2471
  providers: farcasterProviders,
2366
- routes: farcasterWebhookRoutes
2472
+ routes: farcasterWebhookRoutes,
2473
+ autoEnable: {
2474
+ connectorKeys: ["farcaster"]
2475
+ },
2476
+ async init(_config, runtime) {
2477
+ try {
2478
+ const manager = getConnectorAccountManager(runtime);
2479
+ manager.registerProvider(createFarcasterConnectorAccountProvider(runtime));
2480
+ } catch (err) {
2481
+ logger.warn({
2482
+ src: "plugin:farcaster",
2483
+ err: err instanceof Error ? err.message : String(err)
2484
+ }, "Failed to register Farcaster provider with ConnectorAccountManager");
2485
+ }
2486
+ }
2367
2487
  };
2368
- var typescript_default = farcasterPlugin;
2369
2488
  export {
2489
+ resolveDefaultFarcasterAccountId,
2490
+ readFarcasterAccountId,
2491
+ normalizeFarcasterAccountId,
2492
+ listFarcasterAccountIds,
2370
2493
  isEmbedUrl,
2371
2494
  isEmbedCast,
2372
2495
  farcasterPlugin,
2373
- typescript_default as default,
2496
+ default2 as default,
2497
+ createFarcasterConnectorAccountProvider,
2374
2498
  FarcasterService,
2375
2499
  FarcasterClient,
2376
- EmbedManager
2500
+ FARCASTER_PROVIDER_ID,
2501
+ EmbedManager,
2502
+ DEFAULT_FARCASTER_ACCOUNT_ID
2377
2503
  };
2378
2504
 
2379
- //# debugId=A849297018E93F7C64756E2164756E21
2505
+ //# debugId=8F6A0088AE903E9C64756E2164756E21