@framers/agentos 0.1.52 → 0.1.54
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/api/AgentOS.d.ts +10 -10
- package/dist/api/AgentOS.d.ts.map +1 -1
- package/dist/channels/index.d.ts +2 -1
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +2 -1
- package/dist/channels/index.js.map +1 -1
- package/dist/core/agency/index.d.ts +1 -2
- package/dist/core/agency/index.d.ts.map +1 -1
- package/dist/core/agency/index.js +1 -2
- package/dist/core/agency/index.js.map +1 -1
- package/dist/core/agents/AgentCore.d.ts +8 -19
- package/dist/core/agents/AgentCore.d.ts.map +1 -1
- package/dist/core/agents/AgentCore.js +7 -15
- package/dist/core/agents/AgentCore.js.map +1 -1
- package/dist/core/agents/AgentFactory.d.ts +9 -13
- package/dist/core/agents/AgentFactory.d.ts.map +1 -1
- package/dist/core/agents/AgentFactory.js +9 -13
- package/dist/core/agents/AgentFactory.js.map +1 -1
- package/dist/core/audio/SilenceDetector.d.ts +5 -5
- package/dist/core/audio/SilenceDetector.js +5 -5
- package/dist/core/evaluation/index.d.ts +2 -2
- package/dist/core/evaluation/index.js +2 -2
- package/dist/core/hitl/index.d.ts +1 -2
- package/dist/core/hitl/index.d.ts.map +1 -1
- package/dist/core/hitl/index.js +1 -2
- package/dist/core/hitl/index.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/llm/IPromptEngine.d.ts +13 -8
- package/dist/core/llm/IPromptEngine.d.ts.map +1 -1
- package/dist/core/llm/IPromptEngine.js.map +1 -1
- package/dist/core/marketplace/index.d.ts +2 -2
- package/dist/core/marketplace/index.js +2 -2
- package/dist/core/observability/index.d.ts +2 -2
- package/dist/core/observability/index.js +2 -2
- package/dist/core/planning/index.d.ts +1 -2
- package/dist/core/planning/index.d.ts.map +1 -1
- package/dist/core/planning/index.js +1 -2
- package/dist/core/planning/index.js.map +1 -1
- package/dist/core/provenance/index.d.ts +2 -2
- package/dist/core/provenance/index.js +2 -2
- package/dist/core/safety/index.d.ts +3 -2
- package/dist/core/safety/index.d.ts.map +1 -1
- package/dist/core/safety/index.js +3 -2
- package/dist/core/safety/index.js.map +1 -1
- package/dist/core/sandbox/index.d.ts +2 -2
- package/dist/core/sandbox/index.js +2 -2
- package/dist/core/structured/index.d.ts +1 -2
- package/dist/core/structured/index.d.ts.map +1 -1
- package/dist/core/structured/index.js +1 -2
- package/dist/core/structured/index.js.map +1 -1
- package/dist/core/tools/index.d.ts +2 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +2 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/discovery/index.d.ts +2 -1
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +2 -1
- package/dist/discovery/index.js.map +1 -1
- package/dist/memory/index.d.ts +2 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +2 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/rag/audit/index.d.ts +5 -2
- package/dist/rag/audit/index.d.ts.map +1 -1
- package/dist/rag/audit/index.js +5 -2
- package/dist/rag/audit/index.js.map +1 -1
- package/dist/rag/implementations/vector_stores/InMemoryVectorStore.d.ts +1 -2
- package/dist/rag/implementations/vector_stores/InMemoryVectorStore.d.ts.map +1 -1
- package/dist/rag/implementations/vector_stores/InMemoryVectorStore.js +1 -2
- package/dist/rag/implementations/vector_stores/InMemoryVectorStore.js.map +1 -1
- package/dist/rag/reranking/index.d.ts +2 -2
- package/dist/rag/reranking/index.js +2 -2
- package/dist/rag/reranking/providers/index.d.ts +3 -2
- package/dist/rag/reranking/providers/index.d.ts.map +1 -1
- package/dist/rag/reranking/providers/index.js +3 -2
- package/dist/rag/reranking/providers/index.js.map +1 -1
- package/dist/skills/SkillRegistry.js +1 -1
- package/dist/skills/SkillRegistry.js.map +1 -1
- package/dist/skills/index.d.ts +7 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +7 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/social-posting/ContentAdaptationEngine.d.ts +148 -0
- package/dist/social-posting/ContentAdaptationEngine.d.ts.map +1 -0
- package/dist/social-posting/ContentAdaptationEngine.js +445 -0
- package/dist/social-posting/ContentAdaptationEngine.js.map +1 -0
- package/dist/social-posting/SocialAbstractService.d.ts +52 -0
- package/dist/social-posting/SocialAbstractService.d.ts.map +1 -0
- package/dist/social-posting/SocialAbstractService.js +127 -0
- package/dist/social-posting/SocialAbstractService.js.map +1 -0
- package/dist/social-posting/SocialPostManager.d.ts +252 -0
- package/dist/social-posting/SocialPostManager.d.ts.map +1 -0
- package/dist/social-posting/SocialPostManager.js +344 -0
- package/dist/social-posting/SocialPostManager.js.map +1 -0
- package/dist/social-posting/index.d.ts +12 -0
- package/dist/social-posting/index.d.ts.map +1 -0
- package/dist/social-posting/index.js +15 -0
- package/dist/social-posting/index.js.map +1 -0
- package/dist/voice/index.d.ts +2 -1
- package/dist/voice/index.d.ts.map +1 -1
- package/dist/voice/index.js +2 -1
- package/dist/voice/index.js.map +1 -1
- package/package.json +6 -3
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Content Adaptation Engine — platform-specific content transformation.
|
|
3
|
+
* @module @framers/agentos/social-posting/ContentAdaptationEngine
|
|
4
|
+
*
|
|
5
|
+
* Applies static, deterministic rules to adapt a base content string for
|
|
6
|
+
* each target social media platform. The engine enforces character limits,
|
|
7
|
+
* hashtag placement, and generates platform-specific warnings.
|
|
8
|
+
*
|
|
9
|
+
* LLM-powered adaptation (tone rewriting, audience targeting, etc.) is
|
|
10
|
+
* intentionally left to the skill layer. This module provides the
|
|
11
|
+
* constraint-aware foundation that skills can build upon.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { ContentAdaptationEngine } from '../social-posting/index.js';
|
|
16
|
+
*
|
|
17
|
+
* const engine = new ContentAdaptationEngine();
|
|
18
|
+
*
|
|
19
|
+
* const adapted = engine.adaptContent(
|
|
20
|
+
* 'Announcing our new feature! Check it out at https://example.com',
|
|
21
|
+
* ['twitter', 'linkedin', 'instagram'],
|
|
22
|
+
* ['announcement', 'product'],
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* console.log(adapted.twitter.text); // Truncated to 280 chars if needed
|
|
26
|
+
* console.log(adapted.instagram.hashtags); // ['#announcement', '#product']
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Platform Constraints Registry
|
|
31
|
+
// ============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Static constraint definitions for all supported social media platforms.
|
|
34
|
+
*
|
|
35
|
+
* Platforms are grouped by category:
|
|
36
|
+
* - Short-form: twitter, threads, bluesky, mastodon, farcaster
|
|
37
|
+
* - Visual-first: instagram, tiktok, pinterest, youtube
|
|
38
|
+
* - Professional/general: linkedin, facebook, reddit, lemmy
|
|
39
|
+
* - Long-form blogging: devto, hashnode, medium, wordpress
|
|
40
|
+
*/
|
|
41
|
+
const PLATFORM_CONSTRAINTS = {
|
|
42
|
+
// --------------------------------------------------------------------------
|
|
43
|
+
// Short-form Micro-blogging
|
|
44
|
+
// --------------------------------------------------------------------------
|
|
45
|
+
twitter: {
|
|
46
|
+
maxLength: 280,
|
|
47
|
+
hashtagStyle: 'inline',
|
|
48
|
+
maxHashtags: 5,
|
|
49
|
+
supportsMedia: true,
|
|
50
|
+
supportsVideo: true,
|
|
51
|
+
supportsCarousel: false,
|
|
52
|
+
supportsPoll: true,
|
|
53
|
+
supportsThreading: true,
|
|
54
|
+
toneGuidance: 'Concise, punchy, conversational. Use threads for longer content.',
|
|
55
|
+
},
|
|
56
|
+
threads: {
|
|
57
|
+
maxLength: 500,
|
|
58
|
+
hashtagStyle: 'inline',
|
|
59
|
+
maxHashtags: 5,
|
|
60
|
+
supportsMedia: true,
|
|
61
|
+
supportsVideo: true,
|
|
62
|
+
supportsCarousel: true,
|
|
63
|
+
supportsPoll: false,
|
|
64
|
+
supportsThreading: true,
|
|
65
|
+
toneGuidance: 'Conversational, casual, community-oriented.',
|
|
66
|
+
},
|
|
67
|
+
bluesky: {
|
|
68
|
+
maxLength: 300,
|
|
69
|
+
hashtagStyle: 'none',
|
|
70
|
+
maxHashtags: 0,
|
|
71
|
+
supportsMedia: true,
|
|
72
|
+
supportsVideo: true,
|
|
73
|
+
supportsCarousel: false,
|
|
74
|
+
supportsPoll: false,
|
|
75
|
+
supportsThreading: true,
|
|
76
|
+
toneGuidance: 'Authentic, concise. Hashtags use facets (rich text), not inline text.',
|
|
77
|
+
},
|
|
78
|
+
mastodon: {
|
|
79
|
+
maxLength: 500,
|
|
80
|
+
hashtagStyle: 'inline',
|
|
81
|
+
maxHashtags: 10,
|
|
82
|
+
supportsMedia: true,
|
|
83
|
+
supportsVideo: true,
|
|
84
|
+
supportsCarousel: false,
|
|
85
|
+
supportsPoll: true,
|
|
86
|
+
supportsThreading: true,
|
|
87
|
+
toneGuidance: 'Community-conscious, CW (content warning) support encouraged.',
|
|
88
|
+
},
|
|
89
|
+
farcaster: {
|
|
90
|
+
maxLength: 320,
|
|
91
|
+
hashtagStyle: 'none',
|
|
92
|
+
maxHashtags: 0,
|
|
93
|
+
supportsMedia: true,
|
|
94
|
+
supportsVideo: false,
|
|
95
|
+
supportsCarousel: false,
|
|
96
|
+
supportsPoll: false,
|
|
97
|
+
supportsThreading: true,
|
|
98
|
+
toneGuidance: 'Crypto/web3 native, concise, authentic.',
|
|
99
|
+
},
|
|
100
|
+
// --------------------------------------------------------------------------
|
|
101
|
+
// Visual-First Platforms
|
|
102
|
+
// --------------------------------------------------------------------------
|
|
103
|
+
instagram: {
|
|
104
|
+
maxLength: 2200,
|
|
105
|
+
hashtagStyle: 'footer',
|
|
106
|
+
maxHashtags: 30,
|
|
107
|
+
supportsMedia: true,
|
|
108
|
+
supportsVideo: true,
|
|
109
|
+
supportsCarousel: true,
|
|
110
|
+
supportsPoll: false,
|
|
111
|
+
supportsThreading: false,
|
|
112
|
+
toneGuidance: 'Visual-first, storytelling, hashtags in footer block.',
|
|
113
|
+
},
|
|
114
|
+
tiktok: {
|
|
115
|
+
maxLength: 2200,
|
|
116
|
+
hashtagStyle: 'inline',
|
|
117
|
+
maxHashtags: 10,
|
|
118
|
+
supportsMedia: false,
|
|
119
|
+
supportsVideo: true,
|
|
120
|
+
supportsCarousel: false,
|
|
121
|
+
supportsPoll: false,
|
|
122
|
+
supportsThreading: false,
|
|
123
|
+
toneGuidance: 'Trendy, authentic, short-form video focused.',
|
|
124
|
+
},
|
|
125
|
+
pinterest: {
|
|
126
|
+
maxLength: 500,
|
|
127
|
+
hashtagStyle: 'none',
|
|
128
|
+
maxHashtags: 0,
|
|
129
|
+
supportsMedia: true,
|
|
130
|
+
supportsVideo: true,
|
|
131
|
+
supportsCarousel: true,
|
|
132
|
+
supportsPoll: false,
|
|
133
|
+
supportsThreading: false,
|
|
134
|
+
toneGuidance: 'Keyword-rich, descriptive, aspirational. Focus on discoverability.',
|
|
135
|
+
},
|
|
136
|
+
youtube: {
|
|
137
|
+
maxLength: 5000,
|
|
138
|
+
hashtagStyle: 'inline',
|
|
139
|
+
maxHashtags: 15,
|
|
140
|
+
supportsMedia: false,
|
|
141
|
+
supportsVideo: true,
|
|
142
|
+
supportsCarousel: false,
|
|
143
|
+
supportsPoll: true,
|
|
144
|
+
supportsThreading: false,
|
|
145
|
+
toneGuidance: 'Descriptive, SEO-aware. This is the video description field.',
|
|
146
|
+
},
|
|
147
|
+
// --------------------------------------------------------------------------
|
|
148
|
+
// Professional & General Social
|
|
149
|
+
// --------------------------------------------------------------------------
|
|
150
|
+
linkedin: {
|
|
151
|
+
maxLength: 3000,
|
|
152
|
+
hashtagStyle: 'footer',
|
|
153
|
+
maxHashtags: 5,
|
|
154
|
+
supportsMedia: true,
|
|
155
|
+
supportsVideo: true,
|
|
156
|
+
supportsCarousel: true,
|
|
157
|
+
supportsPoll: true,
|
|
158
|
+
supportsThreading: false,
|
|
159
|
+
toneGuidance: 'Professional, thought-leadership, industry insights.',
|
|
160
|
+
},
|
|
161
|
+
facebook: {
|
|
162
|
+
maxLength: 63206,
|
|
163
|
+
hashtagStyle: 'inline',
|
|
164
|
+
maxHashtags: 10,
|
|
165
|
+
supportsMedia: true,
|
|
166
|
+
supportsVideo: true,
|
|
167
|
+
supportsCarousel: true,
|
|
168
|
+
supportsPoll: true,
|
|
169
|
+
supportsThreading: false,
|
|
170
|
+
toneGuidance: 'Casual, community-driven, longer-form acceptable.',
|
|
171
|
+
},
|
|
172
|
+
reddit: {
|
|
173
|
+
maxLength: 40000,
|
|
174
|
+
hashtagStyle: 'none',
|
|
175
|
+
maxHashtags: 0,
|
|
176
|
+
supportsMedia: true,
|
|
177
|
+
supportsVideo: true,
|
|
178
|
+
supportsCarousel: false,
|
|
179
|
+
supportsPoll: true,
|
|
180
|
+
supportsThreading: false,
|
|
181
|
+
toneGuidance: 'Community-specific, informative, no hashtags. Markdown supported.',
|
|
182
|
+
},
|
|
183
|
+
lemmy: {
|
|
184
|
+
maxLength: 10000,
|
|
185
|
+
hashtagStyle: 'none',
|
|
186
|
+
maxHashtags: 0,
|
|
187
|
+
supportsMedia: true,
|
|
188
|
+
supportsVideo: false,
|
|
189
|
+
supportsCarousel: false,
|
|
190
|
+
supportsPoll: false,
|
|
191
|
+
supportsThreading: false,
|
|
192
|
+
toneGuidance: 'Community-focused, federated. Markdown supported.',
|
|
193
|
+
},
|
|
194
|
+
// --------------------------------------------------------------------------
|
|
195
|
+
// Long-Form Blogging
|
|
196
|
+
// --------------------------------------------------------------------------
|
|
197
|
+
devto: {
|
|
198
|
+
maxLength: 100000,
|
|
199
|
+
hashtagStyle: 'none',
|
|
200
|
+
maxHashtags: 0,
|
|
201
|
+
supportsMedia: true,
|
|
202
|
+
supportsVideo: false,
|
|
203
|
+
supportsCarousel: false,
|
|
204
|
+
supportsPoll: false,
|
|
205
|
+
supportsThreading: false,
|
|
206
|
+
toneGuidance: 'Developer-focused, technical, tutorial-style. Markdown/front-matter.',
|
|
207
|
+
},
|
|
208
|
+
hashnode: {
|
|
209
|
+
maxLength: 100000,
|
|
210
|
+
hashtagStyle: 'none',
|
|
211
|
+
maxHashtags: 0,
|
|
212
|
+
supportsMedia: true,
|
|
213
|
+
supportsVideo: false,
|
|
214
|
+
supportsCarousel: false,
|
|
215
|
+
supportsPoll: false,
|
|
216
|
+
supportsThreading: false,
|
|
217
|
+
toneGuidance: 'Developer blogging, technical depth, markdown.',
|
|
218
|
+
},
|
|
219
|
+
medium: {
|
|
220
|
+
maxLength: 100000,
|
|
221
|
+
hashtagStyle: 'none',
|
|
222
|
+
maxHashtags: 0,
|
|
223
|
+
supportsMedia: true,
|
|
224
|
+
supportsVideo: false,
|
|
225
|
+
supportsCarousel: false,
|
|
226
|
+
supportsPoll: false,
|
|
227
|
+
supportsThreading: false,
|
|
228
|
+
toneGuidance: 'Narrative, polished, long-form essays and thought pieces.',
|
|
229
|
+
},
|
|
230
|
+
wordpress: {
|
|
231
|
+
maxLength: 100000,
|
|
232
|
+
hashtagStyle: 'none',
|
|
233
|
+
maxHashtags: 0,
|
|
234
|
+
supportsMedia: true,
|
|
235
|
+
supportsVideo: true,
|
|
236
|
+
supportsCarousel: false,
|
|
237
|
+
supportsPoll: false,
|
|
238
|
+
supportsThreading: false,
|
|
239
|
+
toneGuidance: 'Flexible — depends on blog style. HTML and shortcodes supported.',
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// ContentAdaptationEngine
|
|
244
|
+
// ============================================================================
|
|
245
|
+
/**
|
|
246
|
+
* Adapts base content for multiple social media platforms using static rules.
|
|
247
|
+
*
|
|
248
|
+
* The engine applies character limits, hashtag formatting, and generates
|
|
249
|
+
* warnings when content exceeds platform constraints. It does **not** perform
|
|
250
|
+
* LLM-based rewriting — that responsibility belongs to the skill layer which
|
|
251
|
+
* can call the engine's constraint methods to inform its prompts.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```typescript
|
|
255
|
+
* const engine = new ContentAdaptationEngine();
|
|
256
|
+
* const results = engine.adaptContent(
|
|
257
|
+
* 'We just shipped dark mode! Try it out.',
|
|
258
|
+
* ['twitter', 'linkedin'],
|
|
259
|
+
* ['darkmode', 'shipping'],
|
|
260
|
+
* );
|
|
261
|
+
*
|
|
262
|
+
* // results.twitter.text --> 'We just shipped dark mode! Try it out. #darkmode #shipping'
|
|
263
|
+
* // results.linkedin.text --> 'We just shipped dark mode! Try it out.\n\n#darkmode #shipping'
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export class ContentAdaptationEngine {
|
|
267
|
+
// --------------------------------------------------------------------------
|
|
268
|
+
// Public API
|
|
269
|
+
// --------------------------------------------------------------------------
|
|
270
|
+
/**
|
|
271
|
+
* Adapt base content for one or more target platforms.
|
|
272
|
+
*
|
|
273
|
+
* For each platform, the engine:
|
|
274
|
+
* 1. Formats hashtags according to the platform's hashtagStyle
|
|
275
|
+
* 2. Truncates content if it exceeds the platform's maxLength
|
|
276
|
+
* 3. Generates warnings for any constraint violations
|
|
277
|
+
*
|
|
278
|
+
* @param baseContent - The original, platform-agnostic content
|
|
279
|
+
* @param platforms - Target platform identifiers
|
|
280
|
+
* @param hashtags - Optional hashtags (without '#' prefix)
|
|
281
|
+
* @returns A record mapping each platform to its adapted content
|
|
282
|
+
*/
|
|
283
|
+
adaptContent(baseContent, platforms, hashtags) {
|
|
284
|
+
const results = {};
|
|
285
|
+
for (const platform of platforms) {
|
|
286
|
+
results[platform] = this.adaptForPlatform(baseContent, platform, hashtags);
|
|
287
|
+
}
|
|
288
|
+
return results;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Retrieve the constraint definition for a platform.
|
|
292
|
+
*
|
|
293
|
+
* Returns a default constraint set for unknown platforms (10000 chars,
|
|
294
|
+
* no hashtags, basic media support).
|
|
295
|
+
*
|
|
296
|
+
* @param platform - Platform identifier
|
|
297
|
+
* @returns The PlatformConstraints for the given platform
|
|
298
|
+
*/
|
|
299
|
+
getConstraints(platform) {
|
|
300
|
+
return PLATFORM_CONSTRAINTS[platform] ?? this.getDefaultConstraints();
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Truncate text to a maximum length, appending an ellipsis if truncated.
|
|
304
|
+
*
|
|
305
|
+
* Attempts to break at a word boundary to avoid cutting words mid-token.
|
|
306
|
+
* If no word boundary is found within the last 30 characters, truncates
|
|
307
|
+
* at the hard limit.
|
|
308
|
+
*
|
|
309
|
+
* @param text - The text to truncate
|
|
310
|
+
* @param maxLength - Maximum allowed length (including ellipsis)
|
|
311
|
+
* @returns The truncated text, or the original if within limits
|
|
312
|
+
*/
|
|
313
|
+
truncateWithEllipsis(text, maxLength) {
|
|
314
|
+
if (text.length <= maxLength) {
|
|
315
|
+
return text;
|
|
316
|
+
}
|
|
317
|
+
const ellipsis = '...';
|
|
318
|
+
const targetLength = maxLength - ellipsis.length;
|
|
319
|
+
if (targetLength <= 0) {
|
|
320
|
+
return text.slice(0, maxLength);
|
|
321
|
+
}
|
|
322
|
+
// Try to break at a word boundary
|
|
323
|
+
const truncated = text.slice(0, targetLength);
|
|
324
|
+
const lastSpace = truncated.lastIndexOf(' ');
|
|
325
|
+
// Only break at word boundary if it's within 30 chars of the target
|
|
326
|
+
if (lastSpace > 0 && targetLength - lastSpace < 30) {
|
|
327
|
+
return truncated.slice(0, lastSpace) + ellipsis;
|
|
328
|
+
}
|
|
329
|
+
return truncated + ellipsis;
|
|
330
|
+
}
|
|
331
|
+
// --------------------------------------------------------------------------
|
|
332
|
+
// Internal Helpers
|
|
333
|
+
// --------------------------------------------------------------------------
|
|
334
|
+
/**
|
|
335
|
+
* Adapt content for a single platform.
|
|
336
|
+
*/
|
|
337
|
+
adaptForPlatform(baseContent, platform, hashtags) {
|
|
338
|
+
const constraints = this.getConstraints(platform);
|
|
339
|
+
const warnings = [];
|
|
340
|
+
// Normalize and limit hashtags
|
|
341
|
+
const normalizedHashtags = this.normalizeHashtags(hashtags ?? [], constraints.maxHashtags);
|
|
342
|
+
if (hashtags && hashtags.length > constraints.maxHashtags) {
|
|
343
|
+
warnings.push(`Hashtag count (${hashtags.length}) exceeds platform maximum (${constraints.maxHashtags}). ` +
|
|
344
|
+
`Truncated to ${constraints.maxHashtags}.`);
|
|
345
|
+
}
|
|
346
|
+
// Build the full text with hashtags placed according to platform style
|
|
347
|
+
let fullText = this.formatWithHashtags(baseContent, normalizedHashtags, constraints.hashtagStyle);
|
|
348
|
+
// Check for truncation
|
|
349
|
+
let truncated = false;
|
|
350
|
+
if (fullText.length > constraints.maxLength) {
|
|
351
|
+
// If hashtags are in footer, try truncating just the content
|
|
352
|
+
if (constraints.hashtagStyle === 'footer' &&
|
|
353
|
+
normalizedHashtags.length > 0) {
|
|
354
|
+
const hashtagBlock = this.buildHashtagBlock(normalizedHashtags);
|
|
355
|
+
const contentBudget = constraints.maxLength - hashtagBlock.length - 2; // 2 for '\n\n'
|
|
356
|
+
if (contentBudget > 0) {
|
|
357
|
+
const truncatedContent = this.truncateWithEllipsis(baseContent, contentBudget);
|
|
358
|
+
fullText = truncatedContent + '\n\n' + hashtagBlock;
|
|
359
|
+
truncated = true;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
fullText = this.truncateWithEllipsis(fullText, constraints.maxLength);
|
|
363
|
+
truncated = true;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
fullText = this.truncateWithEllipsis(fullText, constraints.maxLength);
|
|
368
|
+
truncated = true;
|
|
369
|
+
}
|
|
370
|
+
warnings.push(`Content truncated from ${baseContent.length} to fit ${platform}'s ` +
|
|
371
|
+
`${constraints.maxLength} character limit.`);
|
|
372
|
+
}
|
|
373
|
+
// Platform-specific warnings
|
|
374
|
+
if (platform === 'bluesky' && hashtags && hashtags.length > 0) {
|
|
375
|
+
warnings.push('Bluesky uses facets (rich text) for hashtags, not inline text. ' +
|
|
376
|
+
'Hashtags have been omitted from the text. Apply them via the AT Protocol facets API.');
|
|
377
|
+
}
|
|
378
|
+
if (platform === 'mastodon') {
|
|
379
|
+
warnings.push('Consider adding a Content Warning (CW) if the content is sensitive.');
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
platform,
|
|
383
|
+
text: fullText,
|
|
384
|
+
hashtags: normalizedHashtags,
|
|
385
|
+
truncated,
|
|
386
|
+
mediaSupported: constraints.supportsMedia,
|
|
387
|
+
warnings,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Normalize hashtags: ensure '#' prefix, deduplicate, and limit count.
|
|
392
|
+
*/
|
|
393
|
+
normalizeHashtags(hashtags, maxCount) {
|
|
394
|
+
if (maxCount === 0)
|
|
395
|
+
return [];
|
|
396
|
+
const seen = new Set();
|
|
397
|
+
const normalized = [];
|
|
398
|
+
for (const tag of hashtags) {
|
|
399
|
+
const cleaned = tag.startsWith('#') ? tag : `#${tag}`;
|
|
400
|
+
const lower = cleaned.toLowerCase();
|
|
401
|
+
if (!seen.has(lower) && normalized.length < maxCount) {
|
|
402
|
+
seen.add(lower);
|
|
403
|
+
normalized.push(cleaned);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return normalized;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Format content with hashtags according to the platform's style.
|
|
410
|
+
*/
|
|
411
|
+
formatWithHashtags(content, hashtags, style) {
|
|
412
|
+
if (hashtags.length === 0 || style === 'none') {
|
|
413
|
+
return content;
|
|
414
|
+
}
|
|
415
|
+
if (style === 'footer') {
|
|
416
|
+
const hashtagBlock = this.buildHashtagBlock(hashtags);
|
|
417
|
+
return content + '\n\n' + hashtagBlock;
|
|
418
|
+
}
|
|
419
|
+
// Inline: append hashtags after the content with a space
|
|
420
|
+
return content + ' ' + hashtags.join(' ');
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Build a hashtag block string for footer-style placement.
|
|
424
|
+
*/
|
|
425
|
+
buildHashtagBlock(hashtags) {
|
|
426
|
+
return hashtags.join(' ');
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Default constraints for unknown platforms.
|
|
430
|
+
*/
|
|
431
|
+
getDefaultConstraints() {
|
|
432
|
+
return {
|
|
433
|
+
maxLength: 10000,
|
|
434
|
+
hashtagStyle: 'inline',
|
|
435
|
+
maxHashtags: 10,
|
|
436
|
+
supportsMedia: true,
|
|
437
|
+
supportsVideo: true,
|
|
438
|
+
supportsCarousel: false,
|
|
439
|
+
supportsPoll: false,
|
|
440
|
+
supportsThreading: false,
|
|
441
|
+
toneGuidance: 'General purpose. Adapt to the specific platform context.',
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
//# sourceMappingURL=ContentAdaptationEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentAdaptationEngine.js","sourceRoot":"","sources":["../../src/social-posting/ContentAdaptationEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared HTTP primitives for social channel services.
|
|
3
|
+
* @module @framers/agentos/social-posting/SocialAbstractService
|
|
4
|
+
*/
|
|
5
|
+
export interface SocialRequestOptions {
|
|
6
|
+
/** Retry attempts after the initial request. */
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
/** Base delay for exponential backoff (ms). */
|
|
9
|
+
retryDelayMs?: number;
|
|
10
|
+
/** Max delay cap for backoff (ms). */
|
|
11
|
+
maxRetryDelayMs?: number;
|
|
12
|
+
/** Request timeout (ms). */
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
/** Optional override for retryable HTTP status logic. */
|
|
15
|
+
shouldRetryStatus?: (status: number) => boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface SocialServiceConfig {
|
|
18
|
+
/** Minimum spacing between outbound API requests. */
|
|
19
|
+
minRequestIntervalMs?: number;
|
|
20
|
+
/** Default retry attempts after initial request. */
|
|
21
|
+
defaultMaxRetries?: number;
|
|
22
|
+
/** Default base retry delay (ms). */
|
|
23
|
+
defaultRetryDelayMs?: number;
|
|
24
|
+
/** Default request timeout (ms). */
|
|
25
|
+
defaultTimeoutMs?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Base class for social channel service layers.
|
|
29
|
+
*
|
|
30
|
+
* Adds:
|
|
31
|
+
* - request rate-limiting
|
|
32
|
+
* - retry with exponential backoff on retryable failures
|
|
33
|
+
* - JSON request/response helpers
|
|
34
|
+
* - OAuth scope assertion helper
|
|
35
|
+
*/
|
|
36
|
+
export declare abstract class SocialAbstractService {
|
|
37
|
+
private readonly minRequestIntervalMs;
|
|
38
|
+
private readonly defaultMaxRetries;
|
|
39
|
+
private readonly defaultRetryDelayMs;
|
|
40
|
+
private readonly defaultTimeoutMs;
|
|
41
|
+
private lastRequestAt;
|
|
42
|
+
private rateLimiter;
|
|
43
|
+
protected constructor(config?: SocialServiceConfig);
|
|
44
|
+
protected fetchJson<T>(url: string, init?: RequestInit, options?: SocialRequestOptions): Promise<T>;
|
|
45
|
+
protected fetchText(url: string, init?: RequestInit, options?: SocialRequestOptions): Promise<string>;
|
|
46
|
+
protected fetchWithRetry(url: string, init?: RequestInit, options?: SocialRequestOptions): Promise<Response>;
|
|
47
|
+
protected assertScopes(requiredScopes: string[], grantedScopes?: string[]): void;
|
|
48
|
+
private fetchOnce;
|
|
49
|
+
private waitForRateLimit;
|
|
50
|
+
private computeBackoff;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=SocialAbstractService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SocialAbstractService.d.ts","sourceRoot":"","sources":["../../src/social-posting/SocialAbstractService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,oBAAoB;IACnC,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;CACjD;AAED,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oDAAoD;IACpD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qCAAqC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oCAAoC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAYD;;;;;;;;GAQG;AACH,8BAAsB,qBAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,WAAW,CAAoC;IAEvD,SAAS,aAAa,MAAM,GAAE,mBAAwB;cAOtC,SAAS,CAAC,CAAC,EACzB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,CAAC,CAAC;cAWG,SAAS,CACvB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,MAAM,CAAC;cAKF,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,QAAQ,CAAC;IAoCpB,SAAS,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE,aAAa,GAAE,MAAM,EAAO,GAAG,IAAI;YAQtE,SAAS;YAgCT,gBAAgB;IAkB9B,OAAO,CAAC,cAAc;CAIvB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared HTTP primitives for social channel services.
|
|
3
|
+
* @module @framers/agentos/social-posting/SocialAbstractService
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
6
|
+
const DEFAULT_RETRY_DELAY_MS = 300;
|
|
7
|
+
const DEFAULT_MAX_RETRY_DELAY_MS = 5000;
|
|
8
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
9
|
+
const defaultRetryStatus = (status) => status === 429 || status >= 500;
|
|
10
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
/**
|
|
12
|
+
* Base class for social channel service layers.
|
|
13
|
+
*
|
|
14
|
+
* Adds:
|
|
15
|
+
* - request rate-limiting
|
|
16
|
+
* - retry with exponential backoff on retryable failures
|
|
17
|
+
* - JSON request/response helpers
|
|
18
|
+
* - OAuth scope assertion helper
|
|
19
|
+
*/
|
|
20
|
+
export class SocialAbstractService {
|
|
21
|
+
constructor(config = {}) {
|
|
22
|
+
this.lastRequestAt = 0;
|
|
23
|
+
this.rateLimiter = Promise.resolve();
|
|
24
|
+
this.minRequestIntervalMs = Math.max(0, config.minRequestIntervalMs ?? 0);
|
|
25
|
+
this.defaultMaxRetries = Math.max(0, config.defaultMaxRetries ?? DEFAULT_MAX_RETRIES);
|
|
26
|
+
this.defaultRetryDelayMs = Math.max(1, config.defaultRetryDelayMs ?? DEFAULT_RETRY_DELAY_MS);
|
|
27
|
+
this.defaultTimeoutMs = Math.max(1, config.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
28
|
+
}
|
|
29
|
+
async fetchJson(url, init = {}, options = {}) {
|
|
30
|
+
const res = await this.fetchWithRetry(url, init, options);
|
|
31
|
+
const contentType = (res.headers.get('content-type') ?? '').toLowerCase();
|
|
32
|
+
if (contentType.includes('application/json')) {
|
|
33
|
+
return (await res.json());
|
|
34
|
+
}
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
async fetchText(url, init = {}, options = {}) {
|
|
39
|
+
const res = await this.fetchWithRetry(url, init, options);
|
|
40
|
+
return res.text();
|
|
41
|
+
}
|
|
42
|
+
async fetchWithRetry(url, init = {}, options = {}) {
|
|
43
|
+
const maxRetries = options.maxRetries ?? this.defaultMaxRetries;
|
|
44
|
+
const retryDelayMs = options.retryDelayMs ?? this.defaultRetryDelayMs;
|
|
45
|
+
const maxRetryDelayMs = options.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
|
|
46
|
+
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
47
|
+
const shouldRetryStatus = options.shouldRetryStatus ?? defaultRetryStatus;
|
|
48
|
+
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
|
|
49
|
+
let response = null;
|
|
50
|
+
try {
|
|
51
|
+
await this.waitForRateLimit();
|
|
52
|
+
response = await this.fetchOnce(url, init, timeoutMs);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (attempt >= maxRetries) {
|
|
56
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
57
|
+
}
|
|
58
|
+
await wait(this.computeBackoff(attempt, retryDelayMs, maxRetryDelayMs));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (response.ok)
|
|
62
|
+
return response;
|
|
63
|
+
if (attempt < maxRetries && shouldRetryStatus(response.status)) {
|
|
64
|
+
await wait(this.computeBackoff(attempt, retryDelayMs, maxRetryDelayMs));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const body = await response.text().catch(() => '');
|
|
68
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}${body ? `: ${body}` : ''}`);
|
|
69
|
+
}
|
|
70
|
+
throw new Error('Request failed after retry attempts were exhausted.');
|
|
71
|
+
}
|
|
72
|
+
assertScopes(requiredScopes, grantedScopes = []) {
|
|
73
|
+
const granted = new Set(grantedScopes);
|
|
74
|
+
const missing = requiredScopes.filter((scope) => !granted.has(scope));
|
|
75
|
+
if (missing.length === 0)
|
|
76
|
+
return;
|
|
77
|
+
throw new Error(`Missing required OAuth scopes: ${missing.join(', ')}`);
|
|
78
|
+
}
|
|
79
|
+
async fetchOnce(url, init, timeoutMs) {
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
82
|
+
try {
|
|
83
|
+
const normalizedInit = {
|
|
84
|
+
...init,
|
|
85
|
+
signal: controller.signal,
|
|
86
|
+
};
|
|
87
|
+
if (normalizedInit.body &&
|
|
88
|
+
typeof normalizedInit.body === 'object' &&
|
|
89
|
+
!(normalizedInit.body instanceof URLSearchParams) &&
|
|
90
|
+
!(normalizedInit.body instanceof FormData) &&
|
|
91
|
+
!(normalizedInit.body instanceof Blob) &&
|
|
92
|
+
!(normalizedInit.body instanceof ArrayBuffer)) {
|
|
93
|
+
normalizedInit.body = JSON.stringify(normalizedInit.body);
|
|
94
|
+
const headers = new Headers(normalizedInit.headers ?? {});
|
|
95
|
+
if (!headers.has('content-type')) {
|
|
96
|
+
headers.set('content-type', 'application/json');
|
|
97
|
+
}
|
|
98
|
+
normalizedInit.headers = headers;
|
|
99
|
+
}
|
|
100
|
+
return await fetch(url, normalizedInit);
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
clearTimeout(timeout);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async waitForRateLimit() {
|
|
107
|
+
this.rateLimiter = this.rateLimiter.then(async () => {
|
|
108
|
+
if (this.minRequestIntervalMs <= 0) {
|
|
109
|
+
this.lastRequestAt = Date.now();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
const delta = now - this.lastRequestAt;
|
|
114
|
+
const waitMs = Math.max(0, this.minRequestIntervalMs - delta);
|
|
115
|
+
if (waitMs > 0) {
|
|
116
|
+
await wait(waitMs);
|
|
117
|
+
}
|
|
118
|
+
this.lastRequestAt = Date.now();
|
|
119
|
+
});
|
|
120
|
+
return this.rateLimiter;
|
|
121
|
+
}
|
|
122
|
+
computeBackoff(attempt, baseMs, capMs) {
|
|
123
|
+
const raw = baseMs * 2 ** attempt;
|
|
124
|
+
return Math.min(capMs, raw);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=SocialAbstractService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SocialAbstractService.js","sourceRoot":"","sources":["../../src/social-posting/SocialAbstractService.ts"],"names":[],"mappings":"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"}
|