@llm-newsletter-kit/core 2.0.5 → 2.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/README.md CHANGED
@@ -149,6 +149,88 @@ const newsletterId = await generator.generate();
149
149
 
150
150
  👉 **See the reference implementation: https://github.com/heripo-lab/heripo-research-radar**
151
151
 
152
+ ## Customizing LLM Prompts (PromptProvider)
153
+
154
+ The kit ships with built-in prompts designed to cover a wide range of domains. However, these general-purpose prompts may not be flexible enough for specialized requirements. For example:
155
+
156
+ - Your domain uses unique terminology or jargon that the default prompts don't account for
157
+ - You need a specific newsletter tone or structure (e.g., academic style, casual briefing)
158
+ - Tag classification requires domain-specific taxonomy rules
159
+ - Importance scoring needs custom criteria tailored to your industry
160
+
161
+ `PromptProvider` lets you **replace** any built-in prompt — system prompt, user prompt, or both — on a per-stage basis while keeping the rest of the pipeline intact.
162
+
163
+ ### How It Works
164
+
165
+ PromptProvider is organized by pipeline stage. Every field is optional — omitted prompts fall back to the built-in defaults.
166
+
167
+ ```
168
+ PromptProvider
169
+ ├── analysis
170
+ │ ├── classifyTags — Tag classification (system / user)
171
+ │ ├── analyzeImages — Image analysis (system / user)
172
+ │ └── determineImportance — Importance scoring (system / user)
173
+ └── contentGenerate
174
+ └── generateNewsletter — Final newsletter generation (system / user)
175
+ ```
176
+
177
+ Each prompt slot is a `PromptBuilder<TContext>` — an object with optional `system` and `user` functions. The function receives a typed context object containing all the data the default prompt would use (articles, tags, expert fields, dates, etc.), so you have full control over prompt construction.
178
+
179
+ > **Dependency note:** Analysis prompts (classifyTags → analyzeImages → determineImportance) produce data that flows into the content generation prompt. Changing an analysis prompt may indirectly affect the final newsletter output.
180
+
181
+ ### Example
182
+
183
+ ```ts
184
+ import type {
185
+ GenerateNewsletterConfig,
186
+ PromptProvider,
187
+ } from '@llm-newsletter-kit/core';
188
+
189
+ const promptProvider: PromptProvider = {
190
+ analysis: {
191
+ // Override only the system prompt for tag classification
192
+ classifyTags: {
193
+ system: (ctx) =>
194
+ `You are a legal-domain specialist. Classify articles using ` +
195
+ `legal taxonomy standards. Available tags: ${ctx.existTags.join(', ')}. ` +
196
+ `Output language: ${ctx.outputLanguage}.`,
197
+ // user prompt falls back to the built-in default
198
+ },
199
+ // Override importance scoring with domain-specific criteria
200
+ determineImportance: {
201
+ system: (ctx) =>
202
+ `Score article importance for ${ctx.expertFields.join(', ')} professionals. ` +
203
+ `Regulatory changes and court rulings score 8+. ` +
204
+ `Commentary and opinion pieces score 3-5.`,
205
+ user: (ctx) =>
206
+ `Article: ${ctx.targetArticle.title}\n` +
207
+ `Content: ${ctx.targetArticle.detailContent}\n` +
208
+ `Score this article 1-10.`,
209
+ },
210
+ },
211
+ contentGenerate: {
212
+ // Override the newsletter generation prompt entirely
213
+ generateNewsletter: {
214
+ system: (ctx) =>
215
+ `You produce a weekly legal digest for "${ctx.newsletterBrandName}". ` +
216
+ `Write in ${ctx.outputLanguage}. Use formal academic tone.`,
217
+ user: (ctx) =>
218
+ `Publication date: ${ctx.dateService.getPublicationDisplayDateString()}\n\n` +
219
+ ctx.targetArticles
220
+ .map((a) => `- [${a.title}](${a.url}) (score: ${a.importanceScore})`)
221
+ .join('\n'),
222
+ },
223
+ },
224
+ };
225
+
226
+ const config: GenerateNewsletterConfig<string> = {
227
+ // ... other config (contentOptions, dateService, taskService, providers, etc.)
228
+ promptProvider, // Inject custom prompts
229
+ };
230
+ ```
231
+
232
+ For full context types (`ClassifyTagsPromptContext`, `GenerateNewsletterPromptContext`, etc.), see `src/generate-newsletter/models/prompt-provider.ts`.
233
+
152
234
  ## Public API Overview
153
235
 
154
236
  Entry point: `src/index.ts`
@@ -163,6 +245,7 @@ Entry point: `src/index.ts`
163
245
  - AnalysisProvider { classifyTagOptions.model, analyzeImagesOptions.model, determineScoreOptions(model, minimumImportanceScoreRules), fetchUnscoredArticles, fetchTags, update }
164
246
  - ContentGenerateProvider { model and generation options, issueOrder, publicationCriteria, subscribePageUrl, newsletterBrandName, fetchArticleCandidates, htmlTemplate, saveNewsletter }
165
247
  - GenerateNewsletterOptions { logger, llm, chain, previewNewsletter(emailService/emailMessage/fetchNewsletterForPreview) }
248
+ - PromptProvider { analysis?(classifyTags, analyzeImages, determineImportance), contentGenerate?(generateNewsletter) }
166
249
  - Domain models: DateService, EmailService, Newsletter, etc.
167
250
 
168
251
  For detailed field descriptions, see `src/generate-newsletter/models/interfaces.ts` and type definitions under `src/models/*`.
package/dist/index.cjs CHANGED
@@ -193,8 +193,10 @@ class AnalyzeImages extends LLMQuery {
193
193
  .string()
194
194
  .describe('A high-level overview of the general theme, mood, and context conveyed by the images (without specific names, titles, or exact figures from text within images)'),
195
195
  });
196
+ promptBuilder;
196
197
  constructor(config) {
197
198
  super(config);
199
+ this.promptBuilder = config.promptBuilder;
198
200
  }
199
201
  async execute() {
200
202
  if (!this.targetArticle.hasAttachedImage ||
@@ -218,7 +220,17 @@ class AnalyzeImages extends LLMQuery {
218
220
  });
219
221
  return { result: output.imageContext, usage };
220
222
  }
223
+ get promptContext() {
224
+ return {
225
+ expertFields: this.expertFields,
226
+ outputLanguage: this.options.content.outputLanguage,
227
+ targetArticle: this.targetArticle,
228
+ };
229
+ }
221
230
  get systemPrompt() {
231
+ if (this.promptBuilder?.system) {
232
+ return this.promptBuilder.system(this.promptContext);
233
+ }
222
234
  return `# Image Analysis Expert System
223
235
 
224
236
  ## Identity & Expertise
@@ -289,6 +301,12 @@ You are a specialized image analysis expert in: ${this.expertFields.join(', ')}
289
301
  }));
290
302
  }
291
303
  get textMessage() {
304
+ if (this.promptBuilder?.user) {
305
+ return {
306
+ type: 'text',
307
+ text: this.promptBuilder.user(this.promptContext),
308
+ };
309
+ }
292
310
  return {
293
311
  type: 'text',
294
312
  text: `## Analysis Task
@@ -326,9 +344,11 @@ class ClassifyTags extends LLMQuery {
326
344
  tag2: zod.z.string(),
327
345
  tag3: zod.z.string(),
328
346
  });
347
+ promptBuilder;
329
348
  existTags = [];
330
349
  constructor(config) {
331
350
  super(config);
351
+ this.promptBuilder = config.promptBuilder;
332
352
  }
333
353
  async execute({ existTags }) {
334
354
  this.existTags = existTags;
@@ -341,7 +361,18 @@ class ClassifyTags extends LLMQuery {
341
361
  });
342
362
  return { result: output, usage };
343
363
  }
364
+ get promptContext() {
365
+ return {
366
+ expertFields: this.expertFields,
367
+ outputLanguage: this.options.content.outputLanguage,
368
+ targetArticle: this.targetArticle,
369
+ existTags: this.existTags,
370
+ };
371
+ }
344
372
  get systemPrompt() {
373
+ if (this.promptBuilder?.system) {
374
+ return this.promptBuilder.system(this.promptContext);
375
+ }
345
376
  return `You are an AI specializing in analyzing and categorizing articles for professionals in ${this.expertFields.join(', ')}.
346
377
 
347
378
  ## Core Responsibility
@@ -355,7 +386,7 @@ All classifications must be written in ${this.options.content.outputLanguage}.
355
386
  2. **New Tag Criteria**: Create new classifications only when:
356
387
  - Best existing match scores below 80% compatibility
357
388
  - New tag demonstrates versatility across 10+ similar articles
358
- 3. **Naming Standards**:
389
+ 3. **Naming Standards**:
359
390
  - Length: 3-15 characters
360
391
  - Style: Clear, intuitive ${this.options.content.outputLanguage} terms
361
392
  - Balance industry precision with general reader comprehension
@@ -369,6 +400,9 @@ Prioritize in order:
369
400
  - Long-term scalability and maintainability`;
370
401
  }
371
402
  get userPrompt() {
403
+ if (this.promptBuilder?.user) {
404
+ return this.promptBuilder.user(this.promptContext);
405
+ }
372
406
  return `**Task**: Classify this article with 3 optimal detailed tags.
373
407
 
374
408
  **Article Information**
@@ -406,10 +440,12 @@ class DetermineArticleImportance extends LLMQuery {
406
440
  .describe('Article importance score (1-10, 10 is most important)'),
407
441
  });
408
442
  dateService;
443
+ promptBuilder;
409
444
  constructor(config) {
410
445
  super(config);
411
446
  this.minimumImportanceScoreRules = config.minimumImportanceScoreRules ?? [];
412
447
  this.dateService = config.dateService;
448
+ this.promptBuilder = config.promptBuilder;
413
449
  }
414
450
  async execute() {
415
451
  const { output, usage } = await generateObjectByLLM({
@@ -428,7 +464,18 @@ class DetermineArticleImportance extends LLMQuery {
428
464
  get hasHigherMinimumScore() {
429
465
  return this.minPoint > 1;
430
466
  }
467
+ get promptContext() {
468
+ return {
469
+ expertFields: this.expertFields,
470
+ targetArticle: this.targetArticle,
471
+ dateService: this.dateService,
472
+ minimumImportanceScoreRules: this.minimumImportanceScoreRules,
473
+ };
474
+ }
431
475
  get systemPrompt() {
476
+ if (this.promptBuilder?.system) {
477
+ return this.promptBuilder.system(this.promptContext);
478
+ }
432
479
  return `You are an expert in importance evaluation in the field of ${this.expertFields.join(', ')}.
433
480
 
434
481
  Role:
@@ -465,6 +512,9 @@ Important Notes:
465
512
  - Be sensitive to core keywords, events, policies considered important in the field.`;
466
513
  }
467
514
  get userPrompt() {
515
+ if (this.promptBuilder?.user) {
516
+ return this.promptBuilder.user(this.promptContext);
517
+ }
468
518
  return `Please rate the importance of this article from ${this.minPoint} to 10.
469
519
 
470
520
  **Newsletter Publication Date:** ${this.dateService.getPublicationISODateString()}${this.targetArticle.publishedDate
@@ -509,9 +559,11 @@ class PrivateChain extends BaseChain {
509
559
 
510
560
  class ArticleInsightsChain extends PrivateChain {
511
561
  dateService;
562
+ promptProvider;
512
563
  constructor(config) {
513
564
  super(config);
514
565
  this.dateService = config.dateService;
566
+ this.promptProvider = config.promptProvider;
515
567
  }
516
568
  /* istanbul ignore next - pipeline arrow functions are exercised via higher-level tests */
517
569
  get chain() {
@@ -607,7 +659,10 @@ class ArticleInsightsChain extends PrivateChain {
607
659
  },
608
660
  });
609
661
  try {
610
- const classifyTags = new ClassifyTags(this.getLlmQueryConfig(this.provider.classifyTagOptions.model, article));
662
+ const classifyTags = new ClassifyTags({
663
+ ...this.getLlmQueryConfig(this.provider.classifyTagOptions.model, article),
664
+ promptBuilder: this.promptProvider?.classifyTags,
665
+ });
611
666
  const { result: generatedTags } = await classifyTags.execute({
612
667
  existTags,
613
668
  });
@@ -682,7 +737,10 @@ class ArticleInsightsChain extends PrivateChain {
682
737
  },
683
738
  });
684
739
  try {
685
- const analyzeImages = new AnalyzeImages(this.getLlmQueryConfig(this.provider.analyzeImagesOptions.model, article));
740
+ const analyzeImages = new AnalyzeImages({
741
+ ...this.getLlmQueryConfig(this.provider.analyzeImagesOptions.model, article),
742
+ promptBuilder: this.promptProvider?.analyzeImages,
743
+ });
686
744
  const { result: imageContextByLlm } = await analyzeImages.execute();
687
745
  if (imageContextByLlm) {
688
746
  articlesWithImageContext.push({
@@ -781,6 +839,7 @@ class ArticleInsightsChain extends PrivateChain {
781
839
  ...this.getLlmQueryConfig(this.provider.determineScoreOptions.model, article),
782
840
  minimumImportanceScoreRules: this.provider.determineScoreOptions.minimumImportanceScoreRules,
783
841
  dateService: this.dateService,
842
+ promptBuilder: this.promptProvider?.determineImportance,
784
843
  });
785
844
  const { result: importanceScore } = await determineArticleImportance.execute();
786
845
  const processedArticle = {
@@ -845,9 +904,11 @@ class ArticleInsightsChain extends PrivateChain {
845
904
 
846
905
  class AnalysisChain extends Chain {
847
906
  dateService;
907
+ promptProvider;
848
908
  constructor(config) {
849
909
  super(config);
850
910
  this.dateService = config.dateService;
911
+ this.promptProvider = config.promptProvider;
851
912
  }
852
913
  get chain() {
853
914
  return runnables.RunnablePassthrough.assign({
@@ -903,6 +964,7 @@ class AnalysisChain extends Chain {
903
964
  options: this.options,
904
965
  loggingExecutor: new LoggingExecutor(this.logger, this.taskId),
905
966
  dateService: this.dateService,
967
+ promptProvider: this.promptProvider,
906
968
  });
907
969
  return await articleInsightsChain.generateInsights();
908
970
  });
@@ -936,6 +998,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
936
998
  dateService;
937
999
  subscribePageUrl;
938
1000
  newsletterBrandName;
1001
+ promptBuilder;
939
1002
  schema = zod.z.object({
940
1003
  title: zod.z.string().max(70).min(20).describe('Title of the newsletter email'),
941
1004
  content: zod.z.string().describe('Email content in markdown format'),
@@ -961,6 +1024,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
961
1024
  this.dateService = config.dateService;
962
1025
  this.subscribePageUrl = config.subscribePageUrl;
963
1026
  this.newsletterBrandName = config.newsletterBrandName;
1027
+ this.promptBuilder = config.promptBuilder;
964
1028
  }
965
1029
  async execute() {
966
1030
  const { output, usage, finishReason } = await generateObjectByLLM({
@@ -995,7 +1059,22 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
995
1059
  }
996
1060
  return { result: esToolkit.pick(output, ['title', 'content']), usage };
997
1061
  }
1062
+ get promptContext() {
1063
+ return {
1064
+ expertFields: this.expertFields,
1065
+ outputLanguage: this.options.content.outputLanguage,
1066
+ dateService: this.dateService,
1067
+ targetArticles: this.targetArticles,
1068
+ freeFormIntro: this.options.content.freeFormIntro,
1069
+ titleContext: this.options.content.titleContext,
1070
+ subscribePageUrl: this.subscribePageUrl,
1071
+ newsletterBrandName: this.newsletterBrandName,
1072
+ };
1073
+ }
998
1074
  get systemPrompt() {
1075
+ if (this.promptBuilder?.system) {
1076
+ return this.promptBuilder.system(this.promptContext);
1077
+ }
999
1078
  return `You are a newsletter production expert for "${this.newsletterBrandName}" who analyzes and delivers trends in the fields of ${this.expertFields.join(', ')}. Your goal is to provide in-depth analysis that helps industry professionals easily understand complex information and make informed decisions.
1000
1079
 
1001
1080
  Important rule for displaying date ranges: When displaying date ranges, you must use a hyphen (-) instead of a tilde (~). For example, use 'June 1-2, 2025' instead of 'June 1~2, 2025'. The tilde (~) can be rendered as strikethrough in markdown.
@@ -1127,6 +1206,9 @@ Output Format & Requirements:
1127
1206
  ${this.subscribePageUrl ? `- Add \`${this.subscribePageUrl}\`(Subscribe to ${this.newsletterBrandName}) page access link button at appropriate attention-worthy spot for natural recommendation to others.` : ''}`;
1128
1207
  }
1129
1208
  get userPrompt() {
1209
+ if (this.promptBuilder?.user) {
1210
+ return this.promptBuilder.user(this.promptContext);
1211
+ }
1130
1212
  return `Below is the complete list of newly collected ${this.expertFields.join(', ')} related news:
1131
1213
 
1132
1214
  ${this.targetArticles
@@ -1203,9 +1285,11 @@ class ContentGenerateChain extends Chain {
1203
1285
  minimumArticleCountForIssue;
1204
1286
  priorityArticleScoreThreshold;
1205
1287
  htmlTemplate;
1288
+ promptProvider;
1206
1289
  constructor(config) {
1207
1290
  super(config);
1208
1291
  this.dateService = config.dateService;
1292
+ this.promptProvider = config.promptProvider;
1209
1293
  this.minimumArticleCountForIssue =
1210
1294
  config.provider.publicationCriteria?.minimumArticleCountForIssue ?? 5;
1211
1295
  this.priorityArticleScoreThreshold =
@@ -1288,6 +1372,7 @@ class ContentGenerateChain extends Chain {
1288
1372
  subscribePageUrl: this.provider.subscribePageUrl,
1289
1373
  newsletterBrandName: this.provider.newsletterBrandName,
1290
1374
  dateService: this.dateService,
1375
+ promptBuilder: this.promptProvider?.generateNewsletter,
1291
1376
  });
1292
1377
  const { result } = await generateNewsletter.execute();
1293
1378
  return result;
@@ -1777,6 +1862,7 @@ class GenerateNewsletter {
1777
1862
  contentGenerateProvider;
1778
1863
  logger;
1779
1864
  options;
1865
+ promptProvider;
1780
1866
  previewNewsletterOptions;
1781
1867
  /** Independent internal field **/
1782
1868
  taskId = null;
@@ -1820,6 +1906,7 @@ class GenerateNewsletter {
1820
1906
  ...config.options?.chain,
1821
1907
  },
1822
1908
  };
1909
+ this.promptProvider = config.promptProvider;
1823
1910
  // Default logger (no-op)
1824
1911
  this.logger = config.options?.logger ?? {
1825
1912
  info: (_msg) => { },
@@ -1849,6 +1936,7 @@ class GenerateNewsletter {
1849
1936
  options: this.options,
1850
1937
  loggingExecutor,
1851
1938
  dateService: this.dateService,
1939
+ promptProvider: this.promptProvider?.analysis,
1852
1940
  });
1853
1941
  const contentGenerateChain = new ContentGenerateChain({
1854
1942
  logger: this.logger,
@@ -1857,6 +1945,7 @@ class GenerateNewsletter {
1857
1945
  options: this.options,
1858
1946
  loggingExecutor,
1859
1947
  dateService: this.dateService,
1948
+ promptProvider: this.promptProvider?.contentGenerate,
1860
1949
  });
1861
1950
  const taskChain = runnables.RunnableSequence.from([
1862
1951
  crawlingChain.chain,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.d.ts CHANGED
@@ -289,44 +289,6 @@ type ChainOptions = {
289
289
  stopAfterAttempt?: number;
290
290
  };
291
291
 
292
- /**
293
- * Token markers in template.
294
- */
295
- type HtmlTemplateMarkers = {
296
- /**
297
- * Title token
298
- *
299
- * When the title is set to "NEWSLETTER_TITLE", it replaces the "{{NEWSLETTER_TITLE}}" pattern in the template string.
300
- *
301
- * @default "NEWSLETTER_TITLE"
302
- */
303
- title?: string;
304
- /**
305
- * Content HTML token
306
- *
307
- * When content is set to "NEWSLETTER_CONTENT", it replaces the "{{NEWSLETTER_CONTENT}}" pattern in the template string.
308
- *
309
- * @default "NEWSLETTER_CONTENT"
310
- */
311
- content?: string;
312
- };
313
- /**
314
- * String template and marker set
315
- */
316
- type HtmlTemplate = {
317
- /**
318
- * Original template string
319
- */
320
- html: string;
321
- /**
322
- * Uses default markers when not specified
323
- */
324
- markers?: HtmlTemplateMarkers;
325
- };
326
- type RequiredHtmlTemplate = Pick<HtmlTemplate, 'html'> & {
327
- markers: Required<HtmlTemplateMarkers>;
328
- };
329
-
330
292
  /**
331
293
  * Type representing an email address.
332
294
  * @example "user@example.com"
@@ -488,6 +450,108 @@ interface DateService {
488
450
  getPublicationDisplayDateString: () => string;
489
451
  }
490
452
 
453
+ /**
454
+ * A pair of optional prompt builder functions.
455
+ * The returned string **replaces** the built-in default prompt entirely.
456
+ * If omitted, the default prompt is used.
457
+ */
458
+ type PromptBuilder<TContext> = {
459
+ system?: (context: TContext) => string;
460
+ user?: (context: TContext) => string;
461
+ };
462
+ type ClassifyTagsPromptContext = {
463
+ expertFields: string[];
464
+ outputLanguage: string;
465
+ targetArticle: UnscoredArticle;
466
+ existTags: string[];
467
+ };
468
+ type AnalyzeImagesPromptContext = {
469
+ expertFields: string[];
470
+ outputLanguage: string;
471
+ targetArticle: UnscoredArticle;
472
+ };
473
+ type DetermineImportancePromptContext = {
474
+ expertFields: string[];
475
+ targetArticle: UnscoredArticle;
476
+ dateService: DateService;
477
+ minimumImportanceScoreRules: MinimumImportanceScoreRule[];
478
+ };
479
+ type GenerateNewsletterPromptContext = {
480
+ expertFields: string[];
481
+ outputLanguage: string;
482
+ dateService: DateService;
483
+ targetArticles: ArticleForGenerateContent[];
484
+ freeFormIntro?: boolean;
485
+ titleContext?: string;
486
+ subscribePageUrl?: UrlString;
487
+ newsletterBrandName: string;
488
+ };
489
+ /**
490
+ * Optional provider for customizing LLM prompts.
491
+ * Grouped by pipeline stage. All fields are optional —
492
+ * omitted prompts fall back to built-in defaults.
493
+ *
494
+ * **Dependency note:** Analysis prompts (❶❷❸) produce data that flows
495
+ * into the content generation prompt (❹). Changing an analysis prompt
496
+ * may indirectly affect the final newsletter output.
497
+ */
498
+ interface PromptProvider {
499
+ /**
500
+ * Analysis stage prompts (❶ classifyTags → ❷ analyzeImages → ❸ determineImportance).
501
+ * Results feed into the content generation stage.
502
+ */
503
+ analysis?: {
504
+ classifyTags?: PromptBuilder<ClassifyTagsPromptContext>;
505
+ analyzeImages?: PromptBuilder<AnalyzeImagesPromptContext>;
506
+ determineImportance?: PromptBuilder<DetermineImportancePromptContext>;
507
+ };
508
+ /**
509
+ * Content generation stage prompt (❹ generateNewsletter).
510
+ * Produces the final newsletter title and body.
511
+ */
512
+ contentGenerate?: {
513
+ generateNewsletter?: PromptBuilder<GenerateNewsletterPromptContext>;
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Token markers in template.
519
+ */
520
+ type HtmlTemplateMarkers = {
521
+ /**
522
+ * Title token
523
+ *
524
+ * When the title is set to "NEWSLETTER_TITLE", it replaces the "{{NEWSLETTER_TITLE}}" pattern in the template string.
525
+ *
526
+ * @default "NEWSLETTER_TITLE"
527
+ */
528
+ title?: string;
529
+ /**
530
+ * Content HTML token
531
+ *
532
+ * When content is set to "NEWSLETTER_CONTENT", it replaces the "{{NEWSLETTER_CONTENT}}" pattern in the template string.
533
+ *
534
+ * @default "NEWSLETTER_CONTENT"
535
+ */
536
+ content?: string;
537
+ };
538
+ /**
539
+ * String template and marker set
540
+ */
541
+ type HtmlTemplate = {
542
+ /**
543
+ * Original template string
544
+ */
545
+ html: string;
546
+ /**
547
+ * Uses default markers when not specified
548
+ */
549
+ markers?: HtmlTemplateMarkers;
550
+ };
551
+ type RequiredHtmlTemplate = Pick<HtmlTemplate, 'html'> & {
552
+ markers: Required<HtmlTemplateMarkers>;
553
+ };
554
+
491
555
  /**
492
556
  * Publishable final Newsletter entity.
493
557
  */
@@ -807,6 +871,11 @@ type GenerateNewsletterConfig<TaskId> = {
807
871
  * Optional behavior/settings.
808
872
  */
809
873
  options?: GenerateNewsletterOptions;
874
+ /**
875
+ * Optional provider for customizing LLM prompts.
876
+ * When omitted, built-in default prompts are used.
877
+ */
878
+ promptProvider?: PromptProvider;
810
879
  };
811
880
 
812
881
  /**
@@ -822,6 +891,7 @@ declare class GenerateNewsletter<TaskId> {
822
891
  private readonly contentGenerateProvider;
823
892
  private readonly logger;
824
893
  private readonly options;
894
+ private readonly promptProvider?;
825
895
  private readonly previewNewsletterOptions?;
826
896
  /** Independent internal field **/
827
897
  private taskId;
@@ -858,4 +928,4 @@ declare class GenerateNewsletter<TaskId> {
858
928
  }
859
929
 
860
930
  export { DateType, GenerateNewsletter };
861
- export type { AnalysisProvider, AppLogger, ArticleForGenerateContent, ArticleForUpdateByAnalysis, ContentGenerateProvider, CrawlingProvider, CrawlingTarget, CrawlingTargetGroup, DateService, EmailAddress, EmailAttachment, EmailMessage, EmailService, GenerateNewsletterConfig, GenerateNewsletterOptions, HtmlString, HtmlTemplate, HtmlTemplateMarkers, IsoDateString, LogLevel, LogMessage, MarkdownString, MinimumImportanceScoreRule, Newsletter, ParsedTarget, ParsedTargetDetail, ParsedTargetListItem, RequiredHtmlTemplate, TaskService, UniqueIdentifier, UnscoredArticle, UrlString };
931
+ export type { AnalysisProvider, AnalyzeImagesPromptContext, AppLogger, ArticleForGenerateContent, ArticleForUpdateByAnalysis, ClassifyTagsPromptContext, ContentGenerateProvider, CrawlingProvider, CrawlingTarget, CrawlingTargetGroup, DateService, DetermineImportancePromptContext, EmailAddress, EmailAttachment, EmailMessage, EmailService, GenerateNewsletterConfig, GenerateNewsletterOptions, GenerateNewsletterPromptContext, HtmlString, HtmlTemplate, HtmlTemplateMarkers, IsoDateString, LogLevel, LogMessage, MarkdownString, MinimumImportanceScoreRule, Newsletter, ParsedTarget, ParsedTargetDetail, ParsedTargetListItem, PromptBuilder, PromptProvider, RequiredHtmlTemplate, TaskService, UniqueIdentifier, UnscoredArticle, UrlString };
package/dist/index.js CHANGED
@@ -191,8 +191,10 @@ class AnalyzeImages extends LLMQuery {
191
191
  .string()
192
192
  .describe('A high-level overview of the general theme, mood, and context conveyed by the images (without specific names, titles, or exact figures from text within images)'),
193
193
  });
194
+ promptBuilder;
194
195
  constructor(config) {
195
196
  super(config);
197
+ this.promptBuilder = config.promptBuilder;
196
198
  }
197
199
  async execute() {
198
200
  if (!this.targetArticle.hasAttachedImage ||
@@ -216,7 +218,17 @@ class AnalyzeImages extends LLMQuery {
216
218
  });
217
219
  return { result: output.imageContext, usage };
218
220
  }
221
+ get promptContext() {
222
+ return {
223
+ expertFields: this.expertFields,
224
+ outputLanguage: this.options.content.outputLanguage,
225
+ targetArticle: this.targetArticle,
226
+ };
227
+ }
219
228
  get systemPrompt() {
229
+ if (this.promptBuilder?.system) {
230
+ return this.promptBuilder.system(this.promptContext);
231
+ }
220
232
  return `# Image Analysis Expert System
221
233
 
222
234
  ## Identity & Expertise
@@ -287,6 +299,12 @@ You are a specialized image analysis expert in: ${this.expertFields.join(', ')}
287
299
  }));
288
300
  }
289
301
  get textMessage() {
302
+ if (this.promptBuilder?.user) {
303
+ return {
304
+ type: 'text',
305
+ text: this.promptBuilder.user(this.promptContext),
306
+ };
307
+ }
290
308
  return {
291
309
  type: 'text',
292
310
  text: `## Analysis Task
@@ -324,9 +342,11 @@ class ClassifyTags extends LLMQuery {
324
342
  tag2: z.string(),
325
343
  tag3: z.string(),
326
344
  });
345
+ promptBuilder;
327
346
  existTags = [];
328
347
  constructor(config) {
329
348
  super(config);
349
+ this.promptBuilder = config.promptBuilder;
330
350
  }
331
351
  async execute({ existTags }) {
332
352
  this.existTags = existTags;
@@ -339,7 +359,18 @@ class ClassifyTags extends LLMQuery {
339
359
  });
340
360
  return { result: output, usage };
341
361
  }
362
+ get promptContext() {
363
+ return {
364
+ expertFields: this.expertFields,
365
+ outputLanguage: this.options.content.outputLanguage,
366
+ targetArticle: this.targetArticle,
367
+ existTags: this.existTags,
368
+ };
369
+ }
342
370
  get systemPrompt() {
371
+ if (this.promptBuilder?.system) {
372
+ return this.promptBuilder.system(this.promptContext);
373
+ }
343
374
  return `You are an AI specializing in analyzing and categorizing articles for professionals in ${this.expertFields.join(', ')}.
344
375
 
345
376
  ## Core Responsibility
@@ -353,7 +384,7 @@ All classifications must be written in ${this.options.content.outputLanguage}.
353
384
  2. **New Tag Criteria**: Create new classifications only when:
354
385
  - Best existing match scores below 80% compatibility
355
386
  - New tag demonstrates versatility across 10+ similar articles
356
- 3. **Naming Standards**:
387
+ 3. **Naming Standards**:
357
388
  - Length: 3-15 characters
358
389
  - Style: Clear, intuitive ${this.options.content.outputLanguage} terms
359
390
  - Balance industry precision with general reader comprehension
@@ -367,6 +398,9 @@ Prioritize in order:
367
398
  - Long-term scalability and maintainability`;
368
399
  }
369
400
  get userPrompt() {
401
+ if (this.promptBuilder?.user) {
402
+ return this.promptBuilder.user(this.promptContext);
403
+ }
370
404
  return `**Task**: Classify this article with 3 optimal detailed tags.
371
405
 
372
406
  **Article Information**
@@ -404,10 +438,12 @@ class DetermineArticleImportance extends LLMQuery {
404
438
  .describe('Article importance score (1-10, 10 is most important)'),
405
439
  });
406
440
  dateService;
441
+ promptBuilder;
407
442
  constructor(config) {
408
443
  super(config);
409
444
  this.minimumImportanceScoreRules = config.minimumImportanceScoreRules ?? [];
410
445
  this.dateService = config.dateService;
446
+ this.promptBuilder = config.promptBuilder;
411
447
  }
412
448
  async execute() {
413
449
  const { output, usage } = await generateObjectByLLM({
@@ -426,7 +462,18 @@ class DetermineArticleImportance extends LLMQuery {
426
462
  get hasHigherMinimumScore() {
427
463
  return this.minPoint > 1;
428
464
  }
465
+ get promptContext() {
466
+ return {
467
+ expertFields: this.expertFields,
468
+ targetArticle: this.targetArticle,
469
+ dateService: this.dateService,
470
+ minimumImportanceScoreRules: this.minimumImportanceScoreRules,
471
+ };
472
+ }
429
473
  get systemPrompt() {
474
+ if (this.promptBuilder?.system) {
475
+ return this.promptBuilder.system(this.promptContext);
476
+ }
430
477
  return `You are an expert in importance evaluation in the field of ${this.expertFields.join(', ')}.
431
478
 
432
479
  Role:
@@ -463,6 +510,9 @@ Important Notes:
463
510
  - Be sensitive to core keywords, events, policies considered important in the field.`;
464
511
  }
465
512
  get userPrompt() {
513
+ if (this.promptBuilder?.user) {
514
+ return this.promptBuilder.user(this.promptContext);
515
+ }
466
516
  return `Please rate the importance of this article from ${this.minPoint} to 10.
467
517
 
468
518
  **Newsletter Publication Date:** ${this.dateService.getPublicationISODateString()}${this.targetArticle.publishedDate
@@ -507,9 +557,11 @@ class PrivateChain extends BaseChain {
507
557
 
508
558
  class ArticleInsightsChain extends PrivateChain {
509
559
  dateService;
560
+ promptProvider;
510
561
  constructor(config) {
511
562
  super(config);
512
563
  this.dateService = config.dateService;
564
+ this.promptProvider = config.promptProvider;
513
565
  }
514
566
  /* istanbul ignore next - pipeline arrow functions are exercised via higher-level tests */
515
567
  get chain() {
@@ -605,7 +657,10 @@ class ArticleInsightsChain extends PrivateChain {
605
657
  },
606
658
  });
607
659
  try {
608
- const classifyTags = new ClassifyTags(this.getLlmQueryConfig(this.provider.classifyTagOptions.model, article));
660
+ const classifyTags = new ClassifyTags({
661
+ ...this.getLlmQueryConfig(this.provider.classifyTagOptions.model, article),
662
+ promptBuilder: this.promptProvider?.classifyTags,
663
+ });
609
664
  const { result: generatedTags } = await classifyTags.execute({
610
665
  existTags,
611
666
  });
@@ -680,7 +735,10 @@ class ArticleInsightsChain extends PrivateChain {
680
735
  },
681
736
  });
682
737
  try {
683
- const analyzeImages = new AnalyzeImages(this.getLlmQueryConfig(this.provider.analyzeImagesOptions.model, article));
738
+ const analyzeImages = new AnalyzeImages({
739
+ ...this.getLlmQueryConfig(this.provider.analyzeImagesOptions.model, article),
740
+ promptBuilder: this.promptProvider?.analyzeImages,
741
+ });
684
742
  const { result: imageContextByLlm } = await analyzeImages.execute();
685
743
  if (imageContextByLlm) {
686
744
  articlesWithImageContext.push({
@@ -779,6 +837,7 @@ class ArticleInsightsChain extends PrivateChain {
779
837
  ...this.getLlmQueryConfig(this.provider.determineScoreOptions.model, article),
780
838
  minimumImportanceScoreRules: this.provider.determineScoreOptions.minimumImportanceScoreRules,
781
839
  dateService: this.dateService,
840
+ promptBuilder: this.promptProvider?.determineImportance,
782
841
  });
783
842
  const { result: importanceScore } = await determineArticleImportance.execute();
784
843
  const processedArticle = {
@@ -843,9 +902,11 @@ class ArticleInsightsChain extends PrivateChain {
843
902
 
844
903
  class AnalysisChain extends Chain {
845
904
  dateService;
905
+ promptProvider;
846
906
  constructor(config) {
847
907
  super(config);
848
908
  this.dateService = config.dateService;
909
+ this.promptProvider = config.promptProvider;
849
910
  }
850
911
  get chain() {
851
912
  return RunnablePassthrough.assign({
@@ -901,6 +962,7 @@ class AnalysisChain extends Chain {
901
962
  options: this.options,
902
963
  loggingExecutor: new LoggingExecutor(this.logger, this.taskId),
903
964
  dateService: this.dateService,
965
+ promptProvider: this.promptProvider,
904
966
  });
905
967
  return await articleInsightsChain.generateInsights();
906
968
  });
@@ -934,6 +996,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
934
996
  dateService;
935
997
  subscribePageUrl;
936
998
  newsletterBrandName;
999
+ promptBuilder;
937
1000
  schema = z.object({
938
1001
  title: z.string().max(70).min(20).describe('Title of the newsletter email'),
939
1002
  content: z.string().describe('Email content in markdown format'),
@@ -959,6 +1022,7 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
959
1022
  this.dateService = config.dateService;
960
1023
  this.subscribePageUrl = config.subscribePageUrl;
961
1024
  this.newsletterBrandName = config.newsletterBrandName;
1025
+ this.promptBuilder = config.promptBuilder;
962
1026
  }
963
1027
  async execute() {
964
1028
  const { output, usage, finishReason } = await generateObjectByLLM({
@@ -993,7 +1057,22 @@ let GenerateNewsletter$1 = class GenerateNewsletter extends BaseLLMQuery {
993
1057
  }
994
1058
  return { result: pick(output, ['title', 'content']), usage };
995
1059
  }
1060
+ get promptContext() {
1061
+ return {
1062
+ expertFields: this.expertFields,
1063
+ outputLanguage: this.options.content.outputLanguage,
1064
+ dateService: this.dateService,
1065
+ targetArticles: this.targetArticles,
1066
+ freeFormIntro: this.options.content.freeFormIntro,
1067
+ titleContext: this.options.content.titleContext,
1068
+ subscribePageUrl: this.subscribePageUrl,
1069
+ newsletterBrandName: this.newsletterBrandName,
1070
+ };
1071
+ }
996
1072
  get systemPrompt() {
1073
+ if (this.promptBuilder?.system) {
1074
+ return this.promptBuilder.system(this.promptContext);
1075
+ }
997
1076
  return `You are a newsletter production expert for "${this.newsletterBrandName}" who analyzes and delivers trends in the fields of ${this.expertFields.join(', ')}. Your goal is to provide in-depth analysis that helps industry professionals easily understand complex information and make informed decisions.
998
1077
 
999
1078
  Important rule for displaying date ranges: When displaying date ranges, you must use a hyphen (-) instead of a tilde (~). For example, use 'June 1-2, 2025' instead of 'June 1~2, 2025'. The tilde (~) can be rendered as strikethrough in markdown.
@@ -1125,6 +1204,9 @@ Output Format & Requirements:
1125
1204
  ${this.subscribePageUrl ? `- Add \`${this.subscribePageUrl}\`(Subscribe to ${this.newsletterBrandName}) page access link button at appropriate attention-worthy spot for natural recommendation to others.` : ''}`;
1126
1205
  }
1127
1206
  get userPrompt() {
1207
+ if (this.promptBuilder?.user) {
1208
+ return this.promptBuilder.user(this.promptContext);
1209
+ }
1128
1210
  return `Below is the complete list of newly collected ${this.expertFields.join(', ')} related news:
1129
1211
 
1130
1212
  ${this.targetArticles
@@ -1201,9 +1283,11 @@ class ContentGenerateChain extends Chain {
1201
1283
  minimumArticleCountForIssue;
1202
1284
  priorityArticleScoreThreshold;
1203
1285
  htmlTemplate;
1286
+ promptProvider;
1204
1287
  constructor(config) {
1205
1288
  super(config);
1206
1289
  this.dateService = config.dateService;
1290
+ this.promptProvider = config.promptProvider;
1207
1291
  this.minimumArticleCountForIssue =
1208
1292
  config.provider.publicationCriteria?.minimumArticleCountForIssue ?? 5;
1209
1293
  this.priorityArticleScoreThreshold =
@@ -1286,6 +1370,7 @@ class ContentGenerateChain extends Chain {
1286
1370
  subscribePageUrl: this.provider.subscribePageUrl,
1287
1371
  newsletterBrandName: this.provider.newsletterBrandName,
1288
1372
  dateService: this.dateService,
1373
+ promptBuilder: this.promptProvider?.generateNewsletter,
1289
1374
  });
1290
1375
  const { result } = await generateNewsletter.execute();
1291
1376
  return result;
@@ -1775,6 +1860,7 @@ class GenerateNewsletter {
1775
1860
  contentGenerateProvider;
1776
1861
  logger;
1777
1862
  options;
1863
+ promptProvider;
1778
1864
  previewNewsletterOptions;
1779
1865
  /** Independent internal field **/
1780
1866
  taskId = null;
@@ -1818,6 +1904,7 @@ class GenerateNewsletter {
1818
1904
  ...config.options?.chain,
1819
1905
  },
1820
1906
  };
1907
+ this.promptProvider = config.promptProvider;
1821
1908
  // Default logger (no-op)
1822
1909
  this.logger = config.options?.logger ?? {
1823
1910
  info: (_msg) => { },
@@ -1847,6 +1934,7 @@ class GenerateNewsletter {
1847
1934
  options: this.options,
1848
1935
  loggingExecutor,
1849
1936
  dateService: this.dateService,
1937
+ promptProvider: this.promptProvider?.analysis,
1850
1938
  });
1851
1939
  const contentGenerateChain = new ContentGenerateChain({
1852
1940
  logger: this.logger,
@@ -1855,6 +1943,7 @@ class GenerateNewsletter {
1855
1943
  options: this.options,
1856
1944
  loggingExecutor,
1857
1945
  dateService: this.dateService,
1946
+ promptProvider: this.promptProvider?.contentGenerate,
1858
1947
  });
1859
1948
  const taskChain = RunnableSequence.from([
1860
1949
  crawlingChain.chain,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@llm-newsletter-kit/core",
3
3
  "private": false,
4
4
  "type": "module",
5
- "version": "2.0.5",
5
+ "version": "2.1.0",
6
6
  "description": "An extensible framework to automate your entire newsletter workflow. Handles data collection, LLM-based content analysis, and email generation, letting you focus on your unique domain logic.",
7
7
  "main": "dist/index.cjs",
8
8
  "module": "dist/index.js",
@@ -49,36 +49,36 @@
49
49
  "author": "kimhongyeon",
50
50
  "license": "Apache-2.0",
51
51
  "dependencies": {
52
- "@langchain/core": "^1.1.48",
53
- "ai": "^6.0.197",
54
- "es-toolkit": "^1.47.0",
52
+ "@langchain/core": "^1.2.0",
53
+ "ai": "^6.0.208",
54
+ "es-toolkit": "^1.47.1",
55
55
  "jsdom": "^29.1.1",
56
- "juice": "^12.1.0",
57
- "safe-markdown2html": "^1.0.6",
56
+ "juice": "^12.1.1",
57
+ "safe-markdown2html": "^1.0.8",
58
58
  "zod": "^4.4.3"
59
59
  },
60
60
  "devDependencies": {
61
- "@ai-sdk/anthropic": "^3.0.81",
62
- "@ai-sdk/google": "^3.0.80",
63
- "@ai-sdk/openai": "^3.0.68",
64
- "@ai-sdk/togetherai": "^2.0.53",
61
+ "@ai-sdk/anthropic": "^3.0.85",
62
+ "@ai-sdk/google": "^3.0.83",
63
+ "@ai-sdk/openai": "^3.0.73",
64
+ "@ai-sdk/togetherai": "^2.0.56",
65
65
  "@eslint/js": "^10.0.1",
66
66
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
67
67
  "@types/jsdom": "^28.0.3",
68
- "@types/node": "^25.9.2",
69
- "@vitest/coverage-v8": "^4.1.8",
70
- "@vitest/expect": "^4.1.8",
71
- "eslint": "^10.4.1",
68
+ "@types/node": "^26.0.0",
69
+ "@vitest/coverage-v8": "^4.1.9",
70
+ "@vitest/expect": "^4.1.9",
71
+ "eslint": "^10.5.0",
72
72
  "eslint-plugin-unused-imports": "^4.4.1",
73
- "prettier": "^3.8.3",
73
+ "prettier": "^3.8.4",
74
74
  "rimraf": "^6.1.3",
75
- "rollup": "^4.61.1",
75
+ "rollup": "^4.62.2",
76
76
  "rollup-plugin-dts": "^6.4.1",
77
77
  "rollup-plugin-typescript2": "^0.37.0",
78
78
  "tsx": "^4.22.4",
79
79
  "typescript": "^6.0.3",
80
- "typescript-eslint": "^8.60.1",
81
- "vitest": "^4.1.8"
80
+ "typescript-eslint": "^8.61.1",
81
+ "vitest": "^4.1.9"
82
82
  },
83
83
  "repository": {
84
84
  "type": "git",