@promptbook/core 0.114.0-35 → 0.114.0-38

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/esm/index.es.js CHANGED
@@ -26,7 +26,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
26
26
  * @generated
27
27
  * @see https://github.com/webgptorg/promptbook
28
28
  */
29
- const PROMPTBOOK_ENGINE_VERSION = '0.114.0-35';
29
+ const PROMPTBOOK_ENGINE_VERSION = '0.114.0-38';
30
30
  /**
31
31
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
32
32
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -25249,6 +25249,245 @@ function stripInlineKnowledgeMetadata(metadata) {
25249
25249
  return Object.keys(rest).length > 0 ? rest : undefined;
25250
25250
  }
25251
25251
 
25252
+ /**
25253
+ * Pattern matching a markdown section heading (`## Title`) that separates system-message sections
25254
+ *
25255
+ * @private internal constant of `deduplicateSystemMessage`
25256
+ */
25257
+ const SYSTEM_MESSAGE_SECTION_HEADING_PATTERN = /^##(?!#)\s*(.+?)\s*$/;
25258
+ /**
25259
+ * Pattern matching a fenced code block delimiter which suspends section and block splitting
25260
+ *
25261
+ * @private internal constant of `deduplicateSystemMessage`
25262
+ */
25263
+ const CODE_FENCE_PATTERN = /^\s*(?:```|~~~)/;
25264
+ /**
25265
+ * Pattern matching a markdown list item which keeps merged bullet lists visually compact
25266
+ *
25267
+ * @private internal constant of `deduplicateSystemMessage`
25268
+ */
25269
+ const LIST_ITEM_PATTERN = /^\s*(?:[-*+]|\d+[.)])\s/;
25270
+ /**
25271
+ * Removes duplicated instructions from a generated system message
25272
+ *
25273
+ * Commitments are applied one by one, so a commitment used multiple times in one book repeats its whole
25274
+ * section, including the shared guidance preamble. For example two `WRITING RULES` commitments emit the
25275
+ * `## Writing rules` heading and its guidance paragraph twice. This function merges all sections sharing
25276
+ * one heading into the position of their first occurrence and keeps every identical block only once.
25277
+ *
25278
+ * @param systemMessage The assembled system message which may contain repeated sections and blocks
25279
+ * @returns The system message where each section heading and each identical block appears exactly once
25280
+ *
25281
+ * @private internal utility of `createAgentModelRequirementsWithCommitments`
25282
+ */
25283
+ function deduplicateSystemMessage(systemMessage) {
25284
+ if (!systemMessage.trim()) {
25285
+ return systemMessage;
25286
+ }
25287
+ return splitSystemMessageIntoRegions(systemMessage)
25288
+ .reduce(mergeRegionSharingHeading, [])
25289
+ .map(removeDuplicateBlocksFromRegion)
25290
+ .map(renderRegion)
25291
+ .filter((renderedRegion) => renderedRegion !== '')
25292
+ .join('\n\n');
25293
+ }
25294
+ /**
25295
+ * Splits the system message into the intro region and one region per `## Title` section
25296
+ *
25297
+ * @param systemMessage The system message to split
25298
+ * @returns Regions in their original order, without empty ones
25299
+ *
25300
+ * @private internal utility of `deduplicateSystemMessage`
25301
+ */
25302
+ function splitSystemMessageIntoRegions(systemMessage) {
25303
+ const regions = [];
25304
+ let headingLine = null;
25305
+ let bodyLines = [];
25306
+ let isInsideCodeFence = false;
25307
+ for (const line of systemMessage.split(/\r?\n/)) {
25308
+ if (CODE_FENCE_PATTERN.test(line)) {
25309
+ isInsideCodeFence = !isInsideCodeFence;
25310
+ }
25311
+ if (isInsideCodeFence || !SYSTEM_MESSAGE_SECTION_HEADING_PATTERN.test(line)) {
25312
+ bodyLines.push(line);
25313
+ continue;
25314
+ }
25315
+ regions.push(createRegion(headingLine, bodyLines));
25316
+ headingLine = line;
25317
+ bodyLines = [];
25318
+ }
25319
+ regions.push(createRegion(headingLine, bodyLines));
25320
+ return regions.filter((region) => region.headingLine !== null || region.blocks.length > 0);
25321
+ }
25322
+ /**
25323
+ * Creates one region from its heading line and its raw body lines
25324
+ *
25325
+ * @param headingLine The original `## Title` line, or `null` for the intro before the first heading
25326
+ * @param bodyLines The raw lines following the heading line
25327
+ * @returns The region with its body already split into blocks
25328
+ *
25329
+ * @private internal utility of `splitSystemMessageIntoRegions`
25330
+ */
25331
+ function createRegion(headingLine, bodyLines) {
25332
+ return {
25333
+ titleKey: headingLine === null ? null : normalizeSectionTitle(headingLine),
25334
+ headingLine,
25335
+ isBodySeparatedByBlankLine: bodyLines[0] !== undefined && bodyLines[0].trim() === '',
25336
+ blocks: splitBodyIntoBlocks(bodyLines),
25337
+ };
25338
+ }
25339
+ /**
25340
+ * Normalizes a heading line into a key usable for matching sections which belong together
25341
+ *
25342
+ * @param headingLine The original `## Title` line
25343
+ * @returns Lowercased title without the markdown heading prefix
25344
+ *
25345
+ * @private internal utility of `createRegion`
25346
+ */
25347
+ function normalizeSectionTitle(headingLine) {
25348
+ var _a, _b;
25349
+ return ((_b = (_a = SYSTEM_MESSAGE_SECTION_HEADING_PATTERN.exec(headingLine)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : headingLine).toLowerCase();
25350
+ }
25351
+ /**
25352
+ * Splits raw body lines into blocks separated by blank lines while keeping fenced code blocks intact
25353
+ *
25354
+ * @param bodyLines The raw lines of one region body
25355
+ * @returns Non-empty blocks in their original order
25356
+ *
25357
+ * @private internal utility of `createRegion`
25358
+ */
25359
+ function splitBodyIntoBlocks(bodyLines) {
25360
+ const blockTexts = [];
25361
+ let blockLines = [];
25362
+ let isInsideCodeFence = false;
25363
+ for (const line of bodyLines) {
25364
+ if (CODE_FENCE_PATTERN.test(line)) {
25365
+ isInsideCodeFence = !isInsideCodeFence;
25366
+ }
25367
+ if (!isInsideCodeFence && line.trim() === '') {
25368
+ blockTexts.push(blockLines.join('\n'));
25369
+ blockLines = [];
25370
+ continue;
25371
+ }
25372
+ blockLines.push(line);
25373
+ }
25374
+ blockTexts.push(blockLines.join('\n'));
25375
+ return blockTexts
25376
+ .filter((blockText) => blockText.trim() !== '')
25377
+ .map((blockText) => ({ text: blockText, isOpeningMergedSection: false }));
25378
+ }
25379
+ /**
25380
+ * Appends one region to the already merged regions, merging it into an earlier region with the same heading
25381
+ *
25382
+ * @param mergedRegions Regions merged so far, used as the reducer accumulator
25383
+ * @param region The next region in original order
25384
+ * @returns The accumulator with the region either appended or merged into its earlier twin
25385
+ *
25386
+ * @private internal utility of `deduplicateSystemMessage`
25387
+ */
25388
+ function mergeRegionSharingHeading(mergedRegions, region) {
25389
+ const existingRegionIndex = mergedRegions.findIndex((mergedRegion) => region.titleKey !== null && mergedRegion.titleKey === region.titleKey);
25390
+ if (existingRegionIndex === -1) {
25391
+ return [...mergedRegions, region];
25392
+ }
25393
+ return mergedRegions.map((mergedRegion, index) => index !== existingRegionIndex
25394
+ ? mergedRegion
25395
+ : {
25396
+ ...mergedRegion,
25397
+ blocks: [...mergedRegion.blocks, ...markFirstBlockAsOpeningMergedSection(region.blocks)],
25398
+ });
25399
+ }
25400
+ /**
25401
+ * Marks the first block of a merged section occurrence so the merge seam can be rendered as one list
25402
+ *
25403
+ * @param blocks The blocks of the section occurrence being merged into an earlier section
25404
+ * @returns The same blocks with the first one marked as opening a merged section
25405
+ *
25406
+ * @private internal utility of `mergeRegionSharingHeading`
25407
+ */
25408
+ function markFirstBlockAsOpeningMergedSection(blocks) {
25409
+ return blocks.map((block, index) => (index === 0 ? { ...block, isOpeningMergedSection: true } : block));
25410
+ }
25411
+ /**
25412
+ * Keeps only the first occurrence of every identical block inside one region
25413
+ *
25414
+ * @param region The region whose blocks may repeat
25415
+ * @returns The region without repeated blocks
25416
+ *
25417
+ * @private internal utility of `deduplicateSystemMessage`
25418
+ */
25419
+ function removeDuplicateBlocksFromRegion(region) {
25420
+ const alreadyRenderedTexts = new Set();
25421
+ const uniqueBlocks = [];
25422
+ let isMergeSeamPending = false;
25423
+ for (const block of region.blocks) {
25424
+ if (alreadyRenderedTexts.has(block.text)) {
25425
+ // Note: A removed block must still hand its merge seam over to the next block which survives
25426
+ isMergeSeamPending = isMergeSeamPending || block.isOpeningMergedSection;
25427
+ continue;
25428
+ }
25429
+ alreadyRenderedTexts.add(block.text);
25430
+ uniqueBlocks.push({
25431
+ text: block.text,
25432
+ isOpeningMergedSection: block.isOpeningMergedSection || isMergeSeamPending,
25433
+ });
25434
+ isMergeSeamPending = false;
25435
+ }
25436
+ return { ...region, blocks: uniqueBlocks };
25437
+ }
25438
+ /**
25439
+ * Renders one region back into its markdown representation
25440
+ *
25441
+ * @param region The region to render
25442
+ * @returns The heading line together with its joined blocks
25443
+ *
25444
+ * @private internal utility of `deduplicateSystemMessage`
25445
+ */
25446
+ function renderRegion(region) {
25447
+ const body = joinBlocks(region.blocks);
25448
+ if (region.headingLine === null) {
25449
+ return body;
25450
+ }
25451
+ if (body === '') {
25452
+ return region.headingLine;
25453
+ }
25454
+ return `${region.headingLine}${region.isBodySeparatedByBlankLine ? '\n\n' : '\n'}${body}`;
25455
+ }
25456
+ /**
25457
+ * Joins blocks of one region back together
25458
+ *
25459
+ * @param blocks The blocks to join in their original order
25460
+ * @returns The joined body of one region
25461
+ *
25462
+ * @private internal utility of `renderRegion`
25463
+ */
25464
+ function joinBlocks(blocks) {
25465
+ return blocks
25466
+ .map((block, index) => index === 0 ? block.text : `${createBlockSeparator(blocks[index - 1], block)}${block.text}`)
25467
+ .join('');
25468
+ }
25469
+ /**
25470
+ * Chooses the separator between two neighboring blocks
25471
+ *
25472
+ * Blocks are normally separated by a blank line, exactly as they were written. Only where two sections were
25473
+ * merged together their lists are joined into one compact list instead of two separate ones.
25474
+ *
25475
+ * @param previousBlock The block rendered before the separator
25476
+ * @param nextBlock The block rendered after the separator
25477
+ * @returns Either a single or a double newline
25478
+ *
25479
+ * @private internal utility of `joinBlocks`
25480
+ */
25481
+ function createBlockSeparator(previousBlock, nextBlock) {
25482
+ if (!nextBlock.isOpeningMergedSection) {
25483
+ return '\n\n';
25484
+ }
25485
+ const previousBlockLines = previousBlock.text.split('\n');
25486
+ const isListContinuing = LIST_ITEM_PATTERN.test(previousBlockLines[previousBlockLines.length - 1]) &&
25487
+ LIST_ITEM_PATTERN.test(nextBlock.text.split('\n')[0]);
25488
+ return isListContinuing ? '\n' : '\n\n';
25489
+ }
25490
+
25252
25491
  /**
25253
25492
  * Removes single-hash comment lines (`# Comment`) from a system message
25254
25493
  * This is used to clean up the final system message before sending it to the AI model
@@ -25322,14 +25561,14 @@ function createInitialAgentModelRequirements(agentName, modelName) {
25322
25561
  * Performs the final system-message cleanup pass after all other augmentation steps are complete.
25323
25562
  *
25324
25563
  * @param requirements - Fully built requirements before final cleanup.
25325
- * @returns Requirements with comment lines removed from the final system message.
25564
+ * @returns Requirements with comment lines removed and repeated instructions deduplicated in the final system message.
25326
25565
  *
25327
25566
  * @private internal utility of `createAgentModelRequirementsWithCommitments`
25328
25567
  */
25329
25568
  function finalizeRequirements(requirements) {
25330
25569
  return {
25331
25570
  ...requirements,
25332
- systemMessage: removeCommentsFromSystemMessage(requirements.systemMessage),
25571
+ systemMessage: deduplicateSystemMessage(removeCommentsFromSystemMessage(requirements.systemMessage)),
25333
25572
  };
25334
25573
  }
25335
25574