@agentforge-io/connectors-meta 0.1.0
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.
- package/dist/connector.d.ts +80 -0
- package/dist/connector.js +263 -0
- package/dist/http.d.ts +89 -0
- package/dist/http.js +157 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +71 -0
- package/dist/page.d.ts +164 -0
- package/dist/page.js +139 -0
- package/dist/tools/_shared.d.ts +25 -0
- package/dist/tools/_shared.js +31 -0
- package/dist/tools/facebook-pages.d.ts +41 -0
- package/dist/tools/facebook-pages.js +362 -0
- package/dist/tools/instagram.d.ts +42 -0
- package/dist/tools/instagram.js +389 -0
- package/dist/tools/messenger.d.ts +43 -0
- package/dist/tools/messenger.js +320 -0
- package/dist/tools/whatsapp.d.ts +39 -0
- package/dist/tools/whatsapp.js +242 -0
- package/package.json +24 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Facebook Pages tools.
|
|
4
|
+
*
|
|
5
|
+
* Five tools for managing a Page's published content + engagement:
|
|
6
|
+
*
|
|
7
|
+
* 1. fb_list_posts — Page's own posts, newest first
|
|
8
|
+
* 2. fb_get_post — single post with engagement breakdown
|
|
9
|
+
* 3. fb_create_post — publish or schedule a text/link post
|
|
10
|
+
* 4. fb_list_comments — comments on a post
|
|
11
|
+
* 5. fb_reply_comment — reply nested under a comment
|
|
12
|
+
*
|
|
13
|
+
* Token model:
|
|
14
|
+
* - `ctx.getAccessToken()` returns the PAGE access token.
|
|
15
|
+
* - `ctx.metadata.pageId` is the Page id — every endpoint here
|
|
16
|
+
* routes through it.
|
|
17
|
+
*
|
|
18
|
+
* Why `/posts` (Page-authored) and not `/feed` (everything):
|
|
19
|
+
* - `/{page-id}/feed` returns posts the Page made PLUS posts by
|
|
20
|
+
* other people on the Page wall. Agents usually want "what did
|
|
21
|
+
* WE publish?" so `/posts` is the right default; if we ever need
|
|
22
|
+
* the wall, a future `fb_list_feed` can take the same shape.
|
|
23
|
+
*
|
|
24
|
+
* Scheduled posts:
|
|
25
|
+
* - Setting `published: false` + `scheduled_publish_time` (unix
|
|
26
|
+
* seconds) defers publication. Window: 10 minutes to 6 months
|
|
27
|
+
* from now (Meta hard limit; sending outside returns code 100).
|
|
28
|
+
*
|
|
29
|
+
* Media:
|
|
30
|
+
* - This step covers text + link posts only. Photo/video uploads
|
|
31
|
+
* require a separate /photos or /videos endpoint with multipart
|
|
32
|
+
* bodies, which deserves its own tool (`fb_create_photo_post`)
|
|
33
|
+
* and isn't part of the MVP. Posting an external image via the
|
|
34
|
+
* `link` field is supported — Meta scrapes the URL for OG tags
|
|
35
|
+
* and renders the link preview.
|
|
36
|
+
*/
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.fbReplyCommentTool = exports.fbListCommentsTool = exports.fbCreatePostTool = exports.fbGetPostTool = exports.fbListPostsTool = void 0;
|
|
39
|
+
const http_1 = require("../http");
|
|
40
|
+
const _shared_1 = require("./_shared");
|
|
41
|
+
exports.fbListPostsTool = {
|
|
42
|
+
definition: {
|
|
43
|
+
name: 'fb_list_posts',
|
|
44
|
+
description: "List the connected Facebook Page's own published posts, newest " +
|
|
45
|
+
'first. Returns post id, message, link, created time, permalink, ' +
|
|
46
|
+
'is-published flag, scheduled time (when applicable), and ' +
|
|
47
|
+
'engagement totals (likes, comments, shares).',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
limit: {
|
|
52
|
+
type: 'number',
|
|
53
|
+
description: 'Max posts (1-50, default 20).',
|
|
54
|
+
minimum: 1,
|
|
55
|
+
maximum: 50,
|
|
56
|
+
},
|
|
57
|
+
cursor: {
|
|
58
|
+
type: 'string',
|
|
59
|
+
description: 'Pagination cursor from a previous call.',
|
|
60
|
+
},
|
|
61
|
+
includeUnpublished: {
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
description: 'When true, includes scheduled + draft posts that are not ' +
|
|
64
|
+
'yet visible. Default false (published only).',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
build: (ctx) => async (input) => {
|
|
70
|
+
const pageId = (0, _shared_1.resolvePageId)(ctx);
|
|
71
|
+
const accessToken = await ctx.getAccessToken();
|
|
72
|
+
const limit = Math.min(Math.max(Number(input.limit ?? 20), 1), 50);
|
|
73
|
+
const result = await (0, http_1.metaGet)({
|
|
74
|
+
accessToken,
|
|
75
|
+
// Meta exposes scheduled/draft posts only via the alternate
|
|
76
|
+
// `promotable_posts` edge when `is_published=false` is requested.
|
|
77
|
+
// For the published-only default we use the standard `/posts`.
|
|
78
|
+
path: input.includeUnpublished
|
|
79
|
+
? `/${encodeURIComponent(pageId)}/promotable_posts`
|
|
80
|
+
: `/${encodeURIComponent(pageId)}/posts`,
|
|
81
|
+
query: {
|
|
82
|
+
fields: 'id,message,story,created_time,permalink_url,link,scheduled_publish_time,is_published,likes.summary(true).limit(0),comments.summary(true).limit(0),shares',
|
|
83
|
+
limit,
|
|
84
|
+
after: input.cursor,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return JSON.stringify({
|
|
88
|
+
posts: result.data.map((p) => ({
|
|
89
|
+
id: p.id,
|
|
90
|
+
message: p.message,
|
|
91
|
+
story: p.story,
|
|
92
|
+
link: p.link,
|
|
93
|
+
permalink: p.permalink_url,
|
|
94
|
+
createdAt: p.created_time,
|
|
95
|
+
scheduledPublishTime: p.scheduled_publish_time,
|
|
96
|
+
isPublished: p.is_published ?? true,
|
|
97
|
+
engagement: {
|
|
98
|
+
likes: p.likes?.summary?.total_count ?? 0,
|
|
99
|
+
comments: p.comments?.summary?.total_count ?? 0,
|
|
100
|
+
shares: p.shares?.count ?? 0,
|
|
101
|
+
},
|
|
102
|
+
})),
|
|
103
|
+
pageInfo: {
|
|
104
|
+
nextCursor: result.paging?.cursors?.after,
|
|
105
|
+
hasNextPage: Boolean(result.paging?.next),
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
exports.fbGetPostTool = {
|
|
111
|
+
definition: {
|
|
112
|
+
name: 'fb_get_post',
|
|
113
|
+
description: 'Fetch a single Facebook Page post with full engagement + ' +
|
|
114
|
+
'attachments. Returns message, link, permalink, status type, ' +
|
|
115
|
+
'attachment metadata (type, URL, title, media URL), and ' +
|
|
116
|
+
'engagement totals.',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object',
|
|
119
|
+
properties: {
|
|
120
|
+
postId: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Post id from `fb_list_posts`.',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ['postId'],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
build: (ctx) => async (input) => {
|
|
129
|
+
const postId = String(input.postId ?? '').trim();
|
|
130
|
+
if (!postId)
|
|
131
|
+
throw new Error('postId is required.');
|
|
132
|
+
const accessToken = await ctx.getAccessToken();
|
|
133
|
+
const post = await (0, http_1.metaGet)({
|
|
134
|
+
accessToken,
|
|
135
|
+
path: `/${encodeURIComponent(postId)}`,
|
|
136
|
+
query: {
|
|
137
|
+
fields: 'id,message,story,created_time,updated_time,permalink_url,link,scheduled_publish_time,is_published,status_type,attachments{type,media_type,url,title,description,media},likes.summary(true).limit(0),comments.summary(true).limit(0),shares,reactions.summary(true).limit(0)',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
return JSON.stringify({
|
|
141
|
+
id: post.id,
|
|
142
|
+
message: post.message,
|
|
143
|
+
story: post.story,
|
|
144
|
+
link: post.link,
|
|
145
|
+
permalink: post.permalink_url,
|
|
146
|
+
createdAt: post.created_time,
|
|
147
|
+
updatedAt: post.updated_time,
|
|
148
|
+
statusType: post.status_type,
|
|
149
|
+
scheduledPublishTime: post.scheduled_publish_time,
|
|
150
|
+
isPublished: post.is_published ?? true,
|
|
151
|
+
attachments: post.attachments?.data?.map((a) => ({
|
|
152
|
+
type: a.type,
|
|
153
|
+
mediaType: a.media_type,
|
|
154
|
+
url: a.url,
|
|
155
|
+
title: a.title,
|
|
156
|
+
description: a.description,
|
|
157
|
+
mediaUrl: a.media?.image?.src ?? a.media?.source,
|
|
158
|
+
})) ?? [],
|
|
159
|
+
engagement: {
|
|
160
|
+
likes: post.likes?.summary?.total_count ?? 0,
|
|
161
|
+
comments: post.comments?.summary?.total_count ?? 0,
|
|
162
|
+
shares: post.shares?.count ?? 0,
|
|
163
|
+
reactions: post.reactions?.summary?.total_count ?? 0,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
/** Hard window Meta enforces on `scheduled_publish_time`:
|
|
169
|
+
* - earliest: 10 minutes from now
|
|
170
|
+
* - latest: 6 months (≈ 180 days) from now
|
|
171
|
+
* Outside the window returns code 100 with a useless message; we
|
|
172
|
+
* pre-check to give the agent a clearer error. */
|
|
173
|
+
const MIN_SCHEDULE_SECONDS = 10 * 60;
|
|
174
|
+
const MAX_SCHEDULE_SECONDS = 180 * 24 * 60 * 60;
|
|
175
|
+
exports.fbCreatePostTool = {
|
|
176
|
+
definition: {
|
|
177
|
+
name: 'fb_create_post',
|
|
178
|
+
description: 'Publish (or schedule) a text/link post on the connected ' +
|
|
179
|
+
'Facebook Page. For a link post, set `link` — Meta scrapes the ' +
|
|
180
|
+
"URL's OG tags to render the preview. For a scheduled post, set " +
|
|
181
|
+
'`scheduledPublishTime` (unix seconds, 10min–6mo from now). ' +
|
|
182
|
+
'Returns the new post id.',
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {
|
|
186
|
+
message: {
|
|
187
|
+
type: 'string',
|
|
188
|
+
description: 'Post body. At least one of `message` or `link` is required.',
|
|
189
|
+
},
|
|
190
|
+
link: {
|
|
191
|
+
type: 'string',
|
|
192
|
+
description: 'URL to share. Meta scrapes OG tags and renders a link card. ' +
|
|
193
|
+
"At least one of `message` or `link` is required.",
|
|
194
|
+
},
|
|
195
|
+
scheduledPublishTime: {
|
|
196
|
+
type: 'number',
|
|
197
|
+
description: 'Unix seconds at which to publish. Must be between 10 ' +
|
|
198
|
+
'minutes and 6 months from now. When omitted, the post ' +
|
|
199
|
+
'publishes immediately.',
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
build: (ctx) => async (input) => {
|
|
205
|
+
const pageId = (0, _shared_1.resolvePageId)(ctx);
|
|
206
|
+
const accessToken = await ctx.getAccessToken();
|
|
207
|
+
const message = input.message
|
|
208
|
+
? String(input.message).trim()
|
|
209
|
+
: undefined;
|
|
210
|
+
const link = input.link ? String(input.link).trim() : undefined;
|
|
211
|
+
if (!message && !link) {
|
|
212
|
+
throw new Error('At least one of `message` or `link` must be provided.');
|
|
213
|
+
}
|
|
214
|
+
let scheduledPublishTime;
|
|
215
|
+
let published = true;
|
|
216
|
+
if (input.scheduledPublishTime !== undefined) {
|
|
217
|
+
scheduledPublishTime = Number(input.scheduledPublishTime);
|
|
218
|
+
const now = Math.floor(Date.now() / 1000);
|
|
219
|
+
const delta = scheduledPublishTime - now;
|
|
220
|
+
if (!Number.isFinite(scheduledPublishTime) || delta < MIN_SCHEDULE_SECONDS) {
|
|
221
|
+
throw new Error('scheduledPublishTime must be at least 10 minutes in the future ' +
|
|
222
|
+
`(unix seconds; received ${input.scheduledPublishTime}, now=${now}).`);
|
|
223
|
+
}
|
|
224
|
+
if (delta > MAX_SCHEDULE_SECONDS) {
|
|
225
|
+
throw new Error('scheduledPublishTime must be within 6 months of now.');
|
|
226
|
+
}
|
|
227
|
+
published = false;
|
|
228
|
+
}
|
|
229
|
+
const result = await (0, http_1.metaPost)({
|
|
230
|
+
accessToken,
|
|
231
|
+
path: `/${encodeURIComponent(pageId)}/feed`,
|
|
232
|
+
body: {
|
|
233
|
+
...(message ? { message } : {}),
|
|
234
|
+
...(link ? { link } : {}),
|
|
235
|
+
published,
|
|
236
|
+
...(scheduledPublishTime
|
|
237
|
+
? { scheduled_publish_time: scheduledPublishTime }
|
|
238
|
+
: {}),
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
return JSON.stringify({
|
|
242
|
+
postId: result.id,
|
|
243
|
+
published,
|
|
244
|
+
scheduledPublishTime,
|
|
245
|
+
});
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
exports.fbListCommentsTool = {
|
|
249
|
+
definition: {
|
|
250
|
+
name: 'fb_list_comments',
|
|
251
|
+
description: 'List comments on a Facebook Page post (or replies under a ' +
|
|
252
|
+
'specific parent comment). Returns id, message, author, like ' +
|
|
253
|
+
'count, reply count, hidden flag, parent id, and created time.',
|
|
254
|
+
inputSchema: {
|
|
255
|
+
type: 'object',
|
|
256
|
+
properties: {
|
|
257
|
+
postId: {
|
|
258
|
+
type: 'string',
|
|
259
|
+
description: 'Post id from `fb_list_posts`. Required when ' +
|
|
260
|
+
'`parentCommentId` is not set.',
|
|
261
|
+
},
|
|
262
|
+
parentCommentId: {
|
|
263
|
+
type: 'string',
|
|
264
|
+
description: 'Comment id to read replies under. When set, `postId` is ' +
|
|
265
|
+
'ignored.',
|
|
266
|
+
},
|
|
267
|
+
order: {
|
|
268
|
+
type: 'string',
|
|
269
|
+
enum: ['chronological', 'reverse_chronological'],
|
|
270
|
+
description: 'Sort order. Default `reverse_chronological` (newest first).',
|
|
271
|
+
},
|
|
272
|
+
limit: {
|
|
273
|
+
type: 'number',
|
|
274
|
+
description: 'Max comments (1-50, default 25).',
|
|
275
|
+
minimum: 1,
|
|
276
|
+
maximum: 50,
|
|
277
|
+
},
|
|
278
|
+
cursor: {
|
|
279
|
+
type: 'string',
|
|
280
|
+
description: 'Pagination cursor from a previous call.',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
build: (ctx) => async (input) => {
|
|
286
|
+
const accessToken = await ctx.getAccessToken();
|
|
287
|
+
const postId = input.postId;
|
|
288
|
+
const parentCommentId = input.parentCommentId;
|
|
289
|
+
if (!postId && !parentCommentId) {
|
|
290
|
+
throw new Error('Either `postId` or `parentCommentId` must be provided.');
|
|
291
|
+
}
|
|
292
|
+
const limit = Math.min(Math.max(Number(input.limit ?? 25), 1), 50);
|
|
293
|
+
const path = parentCommentId
|
|
294
|
+
? `/${encodeURIComponent(parentCommentId)}/comments`
|
|
295
|
+
: `/${encodeURIComponent(postId)}/comments`;
|
|
296
|
+
const result = await (0, http_1.metaGet)({
|
|
297
|
+
accessToken,
|
|
298
|
+
path,
|
|
299
|
+
query: {
|
|
300
|
+
fields: 'id,message,created_time,from{id,name},like_count,comment_count,is_hidden,parent{id}',
|
|
301
|
+
order: input.order ?? 'reverse_chronological',
|
|
302
|
+
limit,
|
|
303
|
+
after: input.cursor,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
return JSON.stringify({
|
|
307
|
+
comments: result.data.map((c) => ({
|
|
308
|
+
id: c.id,
|
|
309
|
+
message: c.message,
|
|
310
|
+
authorId: c.from?.id,
|
|
311
|
+
authorName: c.from?.name,
|
|
312
|
+
likeCount: c.like_count ?? 0,
|
|
313
|
+
replyCount: c.comment_count ?? 0,
|
|
314
|
+
hidden: c.is_hidden ?? false,
|
|
315
|
+
parentId: c.parent?.id,
|
|
316
|
+
createdAt: c.created_time,
|
|
317
|
+
})),
|
|
318
|
+
pageInfo: {
|
|
319
|
+
nextCursor: result.paging?.cursors?.after,
|
|
320
|
+
hasNextPage: Boolean(result.paging?.next),
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
exports.fbReplyCommentTool = {
|
|
326
|
+
definition: {
|
|
327
|
+
name: 'fb_reply_comment',
|
|
328
|
+
description: "Reply to a comment on a Facebook Page post. The reply nests " +
|
|
329
|
+
'under the target comment as a public child comment. Returns ' +
|
|
330
|
+
'the new comment id.',
|
|
331
|
+
inputSchema: {
|
|
332
|
+
type: 'object',
|
|
333
|
+
properties: {
|
|
334
|
+
commentId: {
|
|
335
|
+
type: 'string',
|
|
336
|
+
description: 'FB comment id to reply to (from `fb_list_comments`).',
|
|
337
|
+
},
|
|
338
|
+
message: {
|
|
339
|
+
type: 'string',
|
|
340
|
+
description: 'Reply text, ≤8000 chars (FB hard limit).',
|
|
341
|
+
maxLength: 8000,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
required: ['commentId', 'message'],
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
build: (ctx) => async (input) => {
|
|
348
|
+
const accessToken = await ctx.getAccessToken();
|
|
349
|
+
const commentId = String(input.commentId ?? '').trim();
|
|
350
|
+
const message = String(input.message ?? '').trim();
|
|
351
|
+
if (!commentId)
|
|
352
|
+
throw new Error('commentId is required.');
|
|
353
|
+
if (!message)
|
|
354
|
+
throw new Error('message is required.');
|
|
355
|
+
const result = await (0, http_1.metaPost)({
|
|
356
|
+
accessToken,
|
|
357
|
+
path: `/${encodeURIComponent(commentId)}/comments`,
|
|
358
|
+
body: { message },
|
|
359
|
+
});
|
|
360
|
+
return JSON.stringify({ commentId: result.id });
|
|
361
|
+
},
|
|
362
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instagram Graph API tools.
|
|
3
|
+
*
|
|
4
|
+
* Six tools covering the MVP scope the agent needs to act as a brand
|
|
5
|
+
* social-media respondent:
|
|
6
|
+
*
|
|
7
|
+
* 1. ig_list_threads — inbox threads, newest first
|
|
8
|
+
* 2. ig_list_messages — messages in a thread
|
|
9
|
+
* 3. ig_send_message — send a DM (24h window applies)
|
|
10
|
+
* 4. ig_list_media — recent feed posts (id + caption + media URL)
|
|
11
|
+
* 5. ig_list_comments — comments on a specific post
|
|
12
|
+
* 6. ig_reply_comment — reply to a comment thread
|
|
13
|
+
*
|
|
14
|
+
* Token model:
|
|
15
|
+
* - `ctx.getAccessToken()` returns the PAGE access token that was
|
|
16
|
+
* persisted at connect-time (NOT the user token). All IG Graph
|
|
17
|
+
* calls accept the page token for the linked IG Business account.
|
|
18
|
+
* - `ctx.metadata.igUserId` is the linked IG Business Account id —
|
|
19
|
+
* the IG user id, not the @-handle. Set by the connect modal when
|
|
20
|
+
* the operator picks the Page.
|
|
21
|
+
*
|
|
22
|
+
* Why these endpoints (not the Basic Display API):
|
|
23
|
+
* - The Basic Display API is being deprecated and only covers PERSONAL
|
|
24
|
+
* IG accounts. The Graph API (what we use) covers Business +
|
|
25
|
+
* Creator accounts via the Page link. For an agent that needs to
|
|
26
|
+
* READ and REPLY, Graph API is the only viable choice.
|
|
27
|
+
*
|
|
28
|
+
* 24h messaging window:
|
|
29
|
+
* - Meta enforces a 24h reply window for unsolicited DMs. Sending
|
|
30
|
+
* after 24h returns error code 10. We surface the constraint in
|
|
31
|
+
* the tool description but don't pre-validate — there's no reliable
|
|
32
|
+
* way to check from the client (the "last user message" timestamp
|
|
33
|
+
* of every conversation would be N+1 calls per send), and the API
|
|
34
|
+
* error is the authoritative answer.
|
|
35
|
+
*/
|
|
36
|
+
import type { ConnectorToolFactory } from '@agentforge-io/core';
|
|
37
|
+
export declare const igListThreadsTool: ConnectorToolFactory;
|
|
38
|
+
export declare const igListMessagesTool: ConnectorToolFactory;
|
|
39
|
+
export declare const igSendMessageTool: ConnectorToolFactory;
|
|
40
|
+
export declare const igListMediaTool: ConnectorToolFactory;
|
|
41
|
+
export declare const igListCommentsTool: ConnectorToolFactory;
|
|
42
|
+
export declare const igReplyCommentTool: ConnectorToolFactory;
|