@promptbook/core 0.112.0-70 → 0.112.0-72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/esm/index.es.js +1240 -1095
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/book-3.0/Book.d.ts +6 -0
  4. package/esm/src/book-components/Chat/utils/getToolCallChipletInfo.test.d.ts +1 -0
  5. package/esm/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +12 -2
  6. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  7. package/esm/src/cli/cli-commands/run/prepareRunCommandResources.d.ts +20 -0
  8. package/esm/src/cli/cli-commands/run/resolveRunInputParameters.d.ts +12 -0
  9. package/esm/src/cli/cli-commands/run/runCommandAction.d.ts +21 -0
  10. package/esm/src/cli/cli-commands/run/runPipelineExecution.d.ts +14 -0
  11. package/esm/src/cli/cli-commands/run.d.ts +1 -1
  12. package/esm/src/conversion/parsePipeline/applyPipelineHead.d.ts +8 -0
  13. package/esm/src/conversion/parsePipeline/createInitialPipelineJson.d.ts +8 -0
  14. package/esm/src/conversion/parsePipeline/createUniqueSectionNameResolver.d.ts +14 -0
  15. package/esm/src/conversion/parsePipeline/defineParameter.d.ts +8 -0
  16. package/esm/src/conversion/parsePipeline/extractPipelineDescription.d.ts +6 -0
  17. package/esm/src/conversion/parsePipeline/finalizeParsedPipeline.d.ts +8 -0
  18. package/esm/src/conversion/parsePipeline/getPipelineIdentification.d.ts +7 -0
  19. package/esm/src/conversion/parsePipeline/parsePreparedPipelineSections.d.ts +18 -0
  20. package/esm/src/conversion/parsePipeline/preparePipelineString.d.ts +8 -0
  21. package/esm/src/conversion/parsePipeline/processPipelineSection.d.ts +9 -0
  22. package/esm/src/version.d.ts +1 -1
  23. package/package.json +1 -1
  24. package/umd/index.umd.js +1240 -1095
  25. package/umd/index.umd.js.map +1 -1
  26. package/umd/src/book-3.0/Book.d.ts +6 -0
  27. package/umd/src/book-components/Chat/utils/getToolCallChipletInfo.test.d.ts +1 -0
  28. package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +12 -2
  29. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  30. package/umd/src/cli/cli-commands/run/prepareRunCommandResources.d.ts +20 -0
  31. package/umd/src/cli/cli-commands/run/resolveRunInputParameters.d.ts +12 -0
  32. package/umd/src/cli/cli-commands/run/runCommandAction.d.ts +21 -0
  33. package/umd/src/cli/cli-commands/run/runPipelineExecution.d.ts +14 -0
  34. package/umd/src/cli/cli-commands/run.d.ts +1 -1
  35. package/umd/src/conversion/parsePipeline/applyPipelineHead.d.ts +8 -0
  36. package/umd/src/conversion/parsePipeline/createInitialPipelineJson.d.ts +8 -0
  37. package/umd/src/conversion/parsePipeline/createUniqueSectionNameResolver.d.ts +14 -0
  38. package/umd/src/conversion/parsePipeline/defineParameter.d.ts +8 -0
  39. package/umd/src/conversion/parsePipeline/extractPipelineDescription.d.ts +6 -0
  40. package/umd/src/conversion/parsePipeline/finalizeParsedPipeline.d.ts +8 -0
  41. package/umd/src/conversion/parsePipeline/getPipelineIdentification.d.ts +7 -0
  42. package/umd/src/conversion/parsePipeline/parsePreparedPipelineSections.d.ts +18 -0
  43. package/umd/src/conversion/parsePipeline/preparePipelineString.d.ts +8 -0
  44. package/umd/src/conversion/parsePipeline/processPipelineSection.d.ts +9 -0
  45. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -28,7 +28,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
28
28
  * @generated
29
29
  * @see https://github.com/webgptorg/promptbook
30
30
  */
31
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-70';
31
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
32
32
  /**
33
33
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
34
34
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -14124,7 +14124,9 @@ const octopus3dAvatarVisual = {
14124
14124
  };
14125
14125
  const mantleRadiusX = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch;
14126
14126
  const mantleRadiusY = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1;
14127
- const mantleRadiusZ = size * morphologyProfile.body.bodyRadiusRatio * (0.9 + (morphologyProfile.body.horizontalStretch - 1) * 0.3);
14127
+ const mantleRadiusZ = size *
14128
+ morphologyProfile.body.bodyRadiusRatio *
14129
+ (0.9 + (morphologyProfile.body.horizontalStretch - 1) * 0.3);
14128
14130
  const underbodyRadiusX = mantleRadiusX * (0.9 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.08);
14129
14131
  const underbodyRadiusY = mantleRadiusY * (0.44 + morphologyProfile.body.lowerDropRatio * 3.1);
14130
14132
  const underbodyRadiusZ = mantleRadiusZ * 0.78;
@@ -14210,7 +14212,11 @@ const octopus3dAvatarVisual = {
14210
14212
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
14211
14213
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
14212
14214
  drawProjectedMouth(context, [
14213
- { x: -mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -mouthHalfWidth, mouthY) },
14215
+ {
14216
+ x: -mouthHalfWidth,
14217
+ y: mouthY,
14218
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -mouthHalfWidth, mouthY),
14219
+ },
14214
14220
  {
14215
14221
  x: size * morphologyProfile.face.mouthCenterOffsetRatio,
14216
14222
  y: mouthY +
@@ -14219,7 +14225,11 @@ const octopus3dAvatarVisual = {
14219
14225
  interaction.gazeY * size * 0.01,
14220
14226
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, size * morphologyProfile.face.mouthCenterOffsetRatio, mouthY),
14221
14227
  },
14222
- { x: mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY) },
14228
+ {
14229
+ x: mouthHalfWidth,
14230
+ y: mouthY,
14231
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY),
14232
+ },
14223
14233
  ], mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, palette, size);
14224
14234
  },
14225
14235
  };
@@ -14380,7 +14390,8 @@ function createOctopusTentacleStrokes(options) {
14380
14390
  z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.3 + sway * size * 0.012,
14381
14391
  };
14382
14392
  const controlPointTwo = {
14383
- x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
14393
+ x: anchorPoint.x +
14394
+ Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
14384
14395
  y: anchorPoint.y + flowLength * 0.66,
14385
14396
  z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.72 + sway * size * 0.02,
14386
14397
  };
@@ -14445,8 +14456,7 @@ function drawTentacleStroke(context, tentacleStroke, palette) {
14445
14456
  context.beginPath();
14446
14457
  context.moveTo(startPoint.x, startPoint.y);
14447
14458
  context.lineTo(endPoint.x, endPoint.y);
14448
- context.strokeStyle =
14449
- tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
14459
+ context.strokeStyle = tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
14450
14460
  context.lineWidth = width;
14451
14461
  context.lineCap = 'round';
14452
14462
  context.stroke();
@@ -22478,6 +22488,212 @@ const DEFAULT_LIST_TIMEOUTS_LIMIT = 20;
22478
22488
  * @private internal USE TIMEOUT constant
22479
22489
  */
22480
22490
  const MAX_LIST_TIMEOUTS_LIMIT = 100;
22491
+ /**
22492
+ * Creates one formatted timeout-argument validation error.
22493
+ *
22494
+ * @private internal utility of USE TIMEOUT
22495
+ */
22496
+ function createTimeoutToolArgsError(message) {
22497
+ return new PipelineExecutionError(spaceTrim$1(`
22498
+ ${message}
22499
+ `));
22500
+ }
22501
+ /**
22502
+ * Normalizes one optional timeout id string.
22503
+ *
22504
+ * @private internal utility of USE TIMEOUT
22505
+ */
22506
+ function normalizeOptionalTimeoutId(value) {
22507
+ return typeof value === 'string' ? value.trim() : '';
22508
+ }
22509
+ /**
22510
+ * Parses timeout target selection for tools that accept either `timeoutId` or `allActive: true`.
22511
+ *
22512
+ * @private internal utility of USE TIMEOUT
22513
+ */
22514
+ function parseTimeoutTargetSelection(args, options) {
22515
+ const timeoutId = normalizeOptionalTimeoutId(args.timeoutId);
22516
+ const allActive = args.allActive === true;
22517
+ if (timeoutId && allActive) {
22518
+ throw createTimeoutToolArgsError(options.bothMessage);
22519
+ }
22520
+ if (allActive) {
22521
+ return { allActive: true };
22522
+ }
22523
+ if (!timeoutId) {
22524
+ throw createTimeoutToolArgsError(options.missingMessage);
22525
+ }
22526
+ return {
22527
+ timeoutId,
22528
+ allActive: false,
22529
+ };
22530
+ }
22531
+ /**
22532
+ * Parses one explicit `dueAt` update value.
22533
+ *
22534
+ * @private internal utility of USE TIMEOUT
22535
+ */
22536
+ function parseOptionalTimeoutDueAt(value) {
22537
+ if (typeof value !== 'string' || value.trim().length === 0) {
22538
+ return undefined;
22539
+ }
22540
+ const normalizedDueAt = value.trim();
22541
+ const dueAtTimestamp = Date.parse(normalizedDueAt);
22542
+ if (!Number.isFinite(dueAtTimestamp)) {
22543
+ throw createTimeoutToolArgsError('Timeout `dueAt` must be one valid ISO timestamp.');
22544
+ }
22545
+ return new Date(dueAtTimestamp).toISOString();
22546
+ }
22547
+ /**
22548
+ * Parses one explicit `extendByMs` update value.
22549
+ *
22550
+ * @private internal utility of USE TIMEOUT
22551
+ */
22552
+ function parseOptionalTimeoutExtendByMs(value) {
22553
+ if (typeof value !== 'number') {
22554
+ return undefined;
22555
+ }
22556
+ if (!Number.isFinite(value) || value <= 0) {
22557
+ throw createTimeoutToolArgsError('Timeout `extendByMs` must be a positive number of milliseconds.');
22558
+ }
22559
+ return Math.floor(value);
22560
+ }
22561
+ /**
22562
+ * Parses one explicit `recurrenceIntervalMs` update value.
22563
+ *
22564
+ * @private internal utility of USE TIMEOUT
22565
+ */
22566
+ function parseOptionalTimeoutRecurrenceInterval(value) {
22567
+ if (value === null) {
22568
+ return null;
22569
+ }
22570
+ if (typeof value !== 'number') {
22571
+ return undefined;
22572
+ }
22573
+ if (!Number.isFinite(value) || value <= 0) {
22574
+ throw createTimeoutToolArgsError('Timeout `recurrenceIntervalMs` must be a positive number of milliseconds or `null`.');
22575
+ }
22576
+ return Math.floor(value);
22577
+ }
22578
+ /**
22579
+ * Parses one explicit `message` update value.
22580
+ *
22581
+ * @private internal utility of USE TIMEOUT
22582
+ */
22583
+ function parseOptionalTimeoutMessage(value) {
22584
+ if (value === null) {
22585
+ return null;
22586
+ }
22587
+ if (typeof value !== 'string') {
22588
+ return undefined;
22589
+ }
22590
+ const normalizedMessage = value.trim();
22591
+ return normalizedMessage.length > 0 ? normalizedMessage : null;
22592
+ }
22593
+ /**
22594
+ * Parses one explicit `parameters` update value.
22595
+ *
22596
+ * @private internal utility of USE TIMEOUT
22597
+ */
22598
+ function parseOptionalTimeoutParameters(value) {
22599
+ if (value === undefined) {
22600
+ return undefined;
22601
+ }
22602
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
22603
+ throw createTimeoutToolArgsError('Timeout `parameters` must be one JSON object.');
22604
+ }
22605
+ return value;
22606
+ }
22607
+ /**
22608
+ * Parses one explicit `paused` update value.
22609
+ *
22610
+ * @private internal utility of USE TIMEOUT
22611
+ */
22612
+ function parseOptionalTimeoutPaused(value) {
22613
+ return typeof value === 'boolean' ? value : undefined;
22614
+ }
22615
+ /**
22616
+ * Parses patch fields for `update_timeout`.
22617
+ *
22618
+ * @private internal utility of USE TIMEOUT
22619
+ */
22620
+ function parseTimeoutUpdatePatch(args) {
22621
+ const patch = {};
22622
+ const dueAt = parseOptionalTimeoutDueAt(args.dueAt);
22623
+ const extendByMs = parseOptionalTimeoutExtendByMs(args.extendByMs);
22624
+ const recurrenceIntervalMs = parseOptionalTimeoutRecurrenceInterval(args.recurrenceIntervalMs);
22625
+ const message = parseOptionalTimeoutMessage(args.message);
22626
+ const parameters = parseOptionalTimeoutParameters(args.parameters);
22627
+ const paused = parseOptionalTimeoutPaused(args.paused);
22628
+ if (dueAt !== undefined) {
22629
+ patch.dueAt = dueAt;
22630
+ }
22631
+ if (extendByMs !== undefined) {
22632
+ patch.extendByMs = extendByMs;
22633
+ }
22634
+ if (patch.dueAt !== undefined && patch.extendByMs !== undefined) {
22635
+ throw createTimeoutToolArgsError('Timeout update cannot include both `dueAt` and `extendByMs`.');
22636
+ }
22637
+ if (recurrenceIntervalMs !== undefined) {
22638
+ patch.recurrenceIntervalMs = recurrenceIntervalMs;
22639
+ }
22640
+ if (message !== undefined) {
22641
+ patch.message = message;
22642
+ }
22643
+ if (parameters !== undefined) {
22644
+ patch.parameters = parameters;
22645
+ }
22646
+ if (paused !== undefined) {
22647
+ patch.paused = paused;
22648
+ }
22649
+ return patch;
22650
+ }
22651
+ /**
22652
+ * Determines whether the patch contains fields that are only supported for single-timeout updates.
22653
+ *
22654
+ * @private internal utility of USE TIMEOUT
22655
+ */
22656
+ function hasSingleTimeoutOnlyPatchFields(patch) {
22657
+ return (patch.dueAt !== undefined ||
22658
+ patch.extendByMs !== undefined ||
22659
+ patch.recurrenceIntervalMs !== undefined ||
22660
+ patch.message !== undefined ||
22661
+ patch.parameters !== undefined);
22662
+ }
22663
+ /**
22664
+ * Parses bulk timeout update arguments.
22665
+ *
22666
+ * @private internal utility of USE TIMEOUT
22667
+ */
22668
+ function parseBulkTimeoutUpdateArgs(patch) {
22669
+ if (patch.paused === undefined) {
22670
+ throw createTimeoutToolArgsError('Bulk timeout update with `allActive: true` requires `paused` to be explicitly set.');
22671
+ }
22672
+ if (hasSingleTimeoutOnlyPatchFields(patch)) {
22673
+ throw createTimeoutToolArgsError('Bulk timeout update only supports the `paused` field.');
22674
+ }
22675
+ return {
22676
+ allActive: true,
22677
+ paused: patch.paused,
22678
+ };
22679
+ }
22680
+ /**
22681
+ * Parses single-timeout update arguments.
22682
+ *
22683
+ * @private internal utility of USE TIMEOUT
22684
+ */
22685
+ function parseSingleTimeoutUpdateArgs(timeoutId, patch) {
22686
+ if (!timeoutId) {
22687
+ throw createTimeoutToolArgsError('Timeout `timeoutId` is required for single-timeout updates.');
22688
+ }
22689
+ if (Object.keys(patch).length === 0) {
22690
+ throw createTimeoutToolArgsError('Timeout update must include at least one editable field.');
22691
+ }
22692
+ return {
22693
+ timeoutId,
22694
+ patch,
22695
+ };
22696
+ }
22481
22697
  /**
22482
22698
  * Parses and validates `USE TIMEOUT` tool arguments.
22483
22699
  *
@@ -22504,22 +22720,14 @@ const parseTimeoutToolArgs = {
22504
22720
  * Parses `cancel_timeout` input.
22505
22721
  */
22506
22722
  cancel(args) {
22507
- const timeoutId = typeof args.timeoutId === 'string' ? args.timeoutId.trim() : '';
22508
- const allActive = args.allActive === true;
22509
- if (timeoutId && allActive) {
22510
- throw new PipelineExecutionError(spaceTrim$1(`
22511
- Timeout cancellation must target either one \`timeoutId\` or \`allActive: true\`, not both.
22512
- `));
22513
- }
22514
- if (allActive) {
22723
+ const target = parseTimeoutTargetSelection(args, {
22724
+ bothMessage: 'Timeout cancellation must target either one `timeoutId` or `allActive: true`, not both.',
22725
+ missingMessage: 'Timeout `timeoutId` is required unless you pass `allActive: true`.',
22726
+ });
22727
+ if (target.allActive) {
22515
22728
  return { allActive: true };
22516
22729
  }
22517
- if (!timeoutId) {
22518
- throw new PipelineExecutionError(spaceTrim$1(`
22519
- Timeout \`timeoutId\` is required unless you pass \`allActive: true\`.
22520
- `));
22521
- }
22522
- return { timeoutId };
22730
+ return { timeoutId: target.timeoutId };
22523
22731
  },
22524
22732
  /**
22525
22733
  * Parses `list_timeouts` input.
@@ -22550,106 +22758,14 @@ const parseTimeoutToolArgs = {
22550
22758
  * Parses `update_timeout` input.
22551
22759
  */
22552
22760
  update(args) {
22553
- const timeoutId = typeof args.timeoutId === 'string' ? args.timeoutId.trim() : '';
22554
- const allActive = args.allActive === true;
22555
- if (timeoutId && allActive) {
22556
- throw new PipelineExecutionError(spaceTrim$1(`
22557
- Timeout update must target either one \`timeoutId\` or \`allActive: true\`, not both.
22558
- `));
22559
- }
22560
- if (!timeoutId && !allActive) {
22561
- throw new PipelineExecutionError(spaceTrim$1(`
22562
- Timeout update requires one \`timeoutId\` or \`allActive: true\`.
22563
- `));
22564
- }
22565
- const patch = {};
22566
- if (typeof args.dueAt === 'string' && args.dueAt.trim().length > 0) {
22567
- const normalizedDueAt = args.dueAt.trim();
22568
- const dueAtTimestamp = Date.parse(normalizedDueAt);
22569
- if (!Number.isFinite(dueAtTimestamp)) {
22570
- throw new PipelineExecutionError(spaceTrim$1(`
22571
- Timeout \`dueAt\` must be one valid ISO timestamp.
22572
- `));
22573
- }
22574
- patch.dueAt = new Date(dueAtTimestamp).toISOString();
22575
- }
22576
- if (typeof args.extendByMs === 'number') {
22577
- if (!Number.isFinite(args.extendByMs) || args.extendByMs <= 0) {
22578
- throw new PipelineExecutionError(spaceTrim$1(`
22579
- Timeout \`extendByMs\` must be a positive number of milliseconds.
22580
- `));
22581
- }
22582
- patch.extendByMs = Math.floor(args.extendByMs);
22583
- }
22584
- if (patch.dueAt !== undefined && patch.extendByMs !== undefined) {
22585
- throw new PipelineExecutionError(spaceTrim$1(`
22586
- Timeout update cannot include both \`dueAt\` and \`extendByMs\`.
22587
- `));
22588
- }
22589
- if (args.recurrenceIntervalMs === null) {
22590
- patch.recurrenceIntervalMs = null;
22591
- }
22592
- else if (typeof args.recurrenceIntervalMs === 'number') {
22593
- if (!Number.isFinite(args.recurrenceIntervalMs) || args.recurrenceIntervalMs <= 0) {
22594
- throw new PipelineExecutionError(spaceTrim$1(`
22595
- Timeout \`recurrenceIntervalMs\` must be a positive number of milliseconds or \`null\`.
22596
- `));
22597
- }
22598
- patch.recurrenceIntervalMs = Math.floor(args.recurrenceIntervalMs);
22599
- }
22600
- if (args.message === null) {
22601
- patch.message = null;
22602
- }
22603
- else if (typeof args.message === 'string') {
22604
- const normalizedMessage = args.message.trim();
22605
- patch.message = normalizedMessage.length > 0 ? normalizedMessage : null;
22606
- }
22607
- if (args.parameters !== undefined) {
22608
- if (!args.parameters || typeof args.parameters !== 'object' || Array.isArray(args.parameters)) {
22609
- throw new PipelineExecutionError(spaceTrim$1(`
22610
- Timeout \`parameters\` must be one JSON object.
22611
- `));
22612
- }
22613
- patch.parameters = args.parameters;
22614
- }
22615
- if (typeof args.paused === 'boolean') {
22616
- patch.paused = args.paused;
22617
- }
22618
- if (allActive) {
22619
- if (patch.paused === undefined) {
22620
- throw new PipelineExecutionError(spaceTrim$1(`
22621
- Bulk timeout update with \`allActive: true\` requires \`paused\` to be explicitly set.
22622
- `));
22623
- }
22624
- const hasSingleOnlyPatch = patch.dueAt !== undefined ||
22625
- patch.extendByMs !== undefined ||
22626
- patch.recurrenceIntervalMs !== undefined ||
22627
- patch.message !== undefined ||
22628
- patch.parameters !== undefined;
22629
- if (hasSingleOnlyPatch) {
22630
- throw new PipelineExecutionError(spaceTrim$1(`
22631
- Bulk timeout update only supports the \`paused\` field.
22632
- `));
22633
- }
22634
- return {
22635
- allActive: true,
22636
- paused: patch.paused,
22637
- };
22638
- }
22639
- if (!timeoutId) {
22640
- throw new PipelineExecutionError(spaceTrim$1(`
22641
- Timeout \`timeoutId\` is required for single-timeout updates.
22642
- `));
22643
- }
22644
- if (Object.keys(patch).length === 0) {
22645
- throw new PipelineExecutionError(spaceTrim$1(`
22646
- Timeout update must include at least one editable field.
22647
- `));
22648
- }
22649
- return {
22650
- timeoutId,
22651
- patch,
22652
- };
22761
+ const target = parseTimeoutTargetSelection(args, {
22762
+ bothMessage: 'Timeout update must target either one `timeoutId` or `allActive: true`, not both.',
22763
+ missingMessage: 'Timeout update requires one `timeoutId` or `allActive: true`.',
22764
+ });
22765
+ const patch = parseTimeoutUpdatePatch(args);
22766
+ return target.allActive
22767
+ ? parseBulkTimeoutUpdateArgs(patch)
22768
+ : parseSingleTimeoutUpdateArgs(target.timeoutId, patch);
22653
22769
  },
22654
22770
  };
22655
22771
 
@@ -27391,6 +27507,20 @@ class Book {
27391
27507
  isComplete: true,
27392
27508
  }));
27393
27509
  }
27510
+ /**
27511
+ * Gets the newest parsed chat message written by the given sender.
27512
+ *
27513
+ * @public exported from `@promptbook/core`
27514
+ */
27515
+ getLatestMessageBySender(sender) {
27516
+ const normalizedSender = normalizeChatMessageSender(sender);
27517
+ for (const message of [...this.getMessages()].reverse()) {
27518
+ if (message.sender === normalizedSender) {
27519
+ return message;
27520
+ }
27521
+ }
27522
+ return null;
27523
+ }
27394
27524
  }
27395
27525
  /**
27396
27526
  * Normalizes one raw Book source to LF line endings.
@@ -28218,118 +28348,75 @@ function isCommitmentSupported(type) {
28218
28348
  }
28219
28349
 
28220
28350
  /**
28221
- * All available task types
28222
- *
28223
- * There is is distinction between task types and section types
28224
- * - Every section in markdown has its SectionType
28225
- * - Some sections are tasks but other can be non-task sections
28226
- *
28227
- * @public exported from `@promptbook/core`
28228
- */
28229
- const TaskTypes = [
28230
- 'PROMPT',
28231
- 'SIMPLE',
28232
- 'SCRIPT',
28233
- 'DIALOG',
28234
- // <- [🅱]
28235
- ];
28236
-
28237
- /**
28238
- * All available sections which are not tasks
28239
- *
28240
- * @public exported from `@promptbook/core`
28241
- */
28242
- const NonTaskSectionTypes = ['EXAMPLE', 'KNOWLEDGE', 'INSTRUMENT', 'ACTION'];
28243
- /**
28244
- * All available section types
28245
- *
28246
- * There is is distinction between task types and section types
28247
- * - Every section in markdown has its SectionType
28248
- * - Some sections are tasks but other can be non-task sections
28351
+ * Parses the boilerplate command
28249
28352
  *
28250
- * @public exported from `@promptbook/core`
28251
- */
28252
- const SectionTypes = [
28253
- ...TaskTypes.map((TaskType) => `${TaskType}_TASK`),
28254
- ...NonTaskSectionTypes,
28255
- ];
28256
-
28257
- /**
28258
- * Parses the knowledge command
28353
+ * Note: @@ This command is used as boilerplate for new commands - it should NOT be used in any `.book` file
28259
28354
  *
28260
28355
  * @see `documentationUrl` for more details
28261
28356
  *
28262
- * @public exported from `@promptbook/editable`
28357
+ * @private within the commands folder
28263
28358
  */
28264
- const knowledgeCommandParser = {
28359
+ const boilerplateCommandParser = {
28265
28360
  /**
28266
28361
  * Name of the command
28267
28362
  */
28268
- name: 'KNOWLEDGE',
28363
+ name: 'BOILERPLATE',
28364
+ /**
28365
+ * Aliases for the BOILERPLATE command
28366
+ */
28367
+ aliasNames: ['BP'],
28269
28368
  /**
28270
28369
  * BOILERPLATE command can be used in:
28271
28370
  */
28272
28371
  isUsedInPipelineHead: true,
28273
- isUsedInPipelineTask: false,
28372
+ isUsedInPipelineTask: true,
28274
28373
  /**
28275
- * Description of the KNOWLEDGE command
28374
+ * Description of the BOILERPLATE command
28276
28375
  */
28277
- description: `Tells promptbook which external knowledge to use`,
28376
+ description: `@@`,
28278
28377
  /**
28279
28378
  * Link to documentation
28280
28379
  */
28281
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/41',
28380
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/@@',
28282
28381
  /**
28283
- * Example usages of the KNOWLEDGE command
28382
+ * Example usages of the BOILERPLATE command
28284
28383
  */
28285
- examples: [
28286
- 'KNOWLEDGE https://www.pavolhejny.com/',
28287
- 'KNOWLEDGE ./hejny-cv.txt',
28288
- 'KNOWLEDGE ./hejny-cv.md',
28289
- 'KNOWLEDGE ./hejny-cv.pdf',
28290
- 'KNOWLEDGE ./hejny-cv.docx',
28291
- // <- TODO: [😿] Allow ONLY files scoped in the (sub)directory NOT ../ and test it
28292
- ],
28384
+ examples: ['BOILERPLATE foo', 'BOILERPLATE bar', 'BP foo', 'BP bar'],
28293
28385
  /**
28294
- * Parses the KNOWLEDGE command
28386
+ * Parses the BOILERPLATE command
28295
28387
  */
28296
28388
  parse(input) {
28297
28389
  const { args } = input;
28298
- const knowledgeSourceContent = spaceTrim$1(args[0] || '');
28299
- if (knowledgeSourceContent === '') {
28300
- throw new ParseError(`Source is not defined`);
28390
+ if (args.length !== 1) {
28391
+ throw new ParseError(`BOILERPLATE command requires exactly one argument`);
28301
28392
  }
28302
- // TODO: [main] !!4 Following checks should be applied every link in the `sourceContent`
28303
- if (knowledgeSourceContent.startsWith('http://')) {
28304
- throw new ParseError(`Source is not secure`);
28305
- }
28306
- if (!(isValidFilePath(knowledgeSourceContent) || isValidUrl(knowledgeSourceContent))) {
28307
- throw new ParseError(`Source not valid`);
28308
- }
28309
- if (knowledgeSourceContent.startsWith('../') ||
28310
- knowledgeSourceContent.startsWith('/') ||
28311
- /^[A-Z]:[\\/]+/i.test(knowledgeSourceContent)) {
28312
- throw new ParseError(`Source cannot be outside of the .book.md folder`);
28393
+ const value = args[0].toLowerCase();
28394
+ if (value.includes('brr')) {
28395
+ throw new ParseError(`BOILERPLATE value can not contain brr`);
28313
28396
  }
28314
28397
  return {
28315
- type: 'KNOWLEDGE',
28316
- knowledgeSourceContent,
28398
+ type: 'BOILERPLATE',
28399
+ value,
28317
28400
  };
28318
28401
  },
28319
28402
  /**
28320
- * Apply the KNOWLEDGE command to the `pipelineJson`
28403
+ * Apply the BOILERPLATE command to the `pipelineJson`
28321
28404
  *
28322
28405
  * Note: `$` is used to indicate that this function mutates given `pipelineJson`
28323
28406
  */
28324
28407
  $applyToPipelineJson(command, $pipelineJson) {
28325
- const { knowledgeSourceContent } = command;
28326
- $pipelineJson.knowledgeSources.push({
28327
- name: knowledgeSourceContentToName(knowledgeSourceContent),
28328
- knowledgeSourceContent,
28329
- });
28408
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28330
28409
  },
28331
28410
  /**
28332
- * Converts the KNOWLEDGE command back to string
28411
+ * Apply the BOILERPLATE command to the `pipelineJson`
28412
+ *
28413
+ * Note: `$` is used to indicate that this function mutates given `taskJson`
28414
+ */
28415
+ $applyToTaskJson(command, $taskJson, $pipelineJson) {
28416
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28417
+ },
28418
+ /**
28419
+ * Converts the BOILERPLATE command back to string
28333
28420
  *
28334
28421
  * Note: This is used in `pipelineJsonToString` utility
28335
28422
  */
@@ -28337,179 +28424,84 @@ const knowledgeCommandParser = {
28337
28424
  return `---`; // <- TODO: [🛋] Implement
28338
28425
  },
28339
28426
  /**
28340
- * Reads the KNOWLEDGE command from the `PipelineJson`
28427
+ * Reads the BOILERPLATE command from the `PipelineJson`
28341
28428
  *
28342
28429
  * Note: This is used in `pipelineJsonToString` utility
28343
28430
  */
28344
28431
  takeFromPipelineJson(pipelineJson) {
28345
- throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
28432
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28433
+ },
28434
+ /**
28435
+ * Reads the BOILERPLATE command from the `TaskJson`
28436
+ *
28437
+ * Note: This is used in `pipelineJsonToString` utility
28438
+ */
28439
+ takeFromTaskJson($taskJson) {
28440
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28346
28441
  },
28347
28442
  };
28348
- // Note: [⛱] There are two types of KNOWLEDGE commands *...(read more in [⛱])*
28349
28443
 
28350
28444
  /**
28351
- * Parses the section command
28445
+ * Parses the BOOK_VERSION command
28352
28446
  *
28353
28447
  * @see `documentationUrl` for more details
28354
28448
  *
28355
28449
  * @public exported from `@promptbook/editable`
28356
28450
  */
28357
- const sectionCommandParser = {
28451
+ const bookVersionCommandParser = {
28358
28452
  /**
28359
28453
  * Name of the command
28360
28454
  */
28361
- name: 'SECTION',
28362
- /**
28363
- * Aliases for the SECTION command
28364
- */
28365
- aliasNames: [
28366
- 'PROMPT',
28367
- 'SIMPLE',
28368
- 'SCRIPT',
28369
- 'DIALOG',
28370
- 'SAMPLE',
28371
- 'EXAMPLE',
28372
- 'KNOWLEDGE',
28373
- 'INSTRUMENT',
28374
- 'ACTION', // <- Note: [⛱]
28375
- ],
28376
- /**
28377
- * Aliases for the SECTION command
28378
- */
28379
- deprecatedNames: ['TEMPLATE', 'BLOCK', 'EXECUTE'],
28455
+ name: 'BOOK_VERSION',
28456
+ aliasNames: ['PTBK_VERSION', 'PROMPTBOOK_VERSION', 'BOOK'],
28380
28457
  /**
28381
28458
  * BOILERPLATE command can be used in:
28382
28459
  */
28383
- isUsedInPipelineHead: false,
28384
- isUsedInPipelineTask: true,
28460
+ isUsedInPipelineHead: true,
28461
+ isUsedInPipelineTask: false,
28385
28462
  /**
28386
- * Description of the SECTION command
28463
+ * Description of the BOOK_VERSION command
28387
28464
  */
28388
- description: `Defines the purpose of the markdown section - if its a task and which type or something else`,
28465
+ description: `Which version of the Book language is the .book.md using`,
28389
28466
  /**
28390
28467
  * Link to documentation
28391
28468
  */
28392
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/64',
28469
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/69',
28393
28470
  /**
28394
- * Example usages of the SECTION command
28471
+ * Example usages of the BOOK_VERSION command
28395
28472
  */
28396
- examples: [
28397
- // Short form:
28398
- 'PROMPT',
28399
- 'SIMPLE',
28400
- 'SCRIPT',
28401
- 'DIALOG',
28402
- // <- [🅱]
28403
- 'EXAMPLE',
28404
- 'KNOWLEDGE',
28405
- 'INSTRUMENT',
28406
- 'ACTION',
28407
- // -----------------
28408
- // Recommended (reversed) form:
28409
- 'PROMPT SECTION',
28410
- 'SIMPLE SECTION',
28411
- 'SCRIPT SECTION',
28412
- 'DIALOG SECTION',
28413
- // <- [🅱]
28414
- 'EXAMPLE SECTION',
28415
- 'KNOWLEDGE SECTION',
28416
- 'INSTRUMENT SECTION',
28417
- 'ACTION SECTION',
28418
- // -----------------
28419
- // Standard form:
28420
- 'SECTION PROMPT',
28421
- 'SECTION SIMPLE',
28422
- 'SECTION SCRIPT',
28423
- 'SECTION DIALOG',
28424
- // <- [🅱]
28425
- 'SECTION EXAMPLE',
28426
- 'SECTION KNOWLEDGE',
28427
- 'SECTION INSTRUMENT',
28428
- 'SECTION ACTION',
28429
- ],
28430
- // TODO: [♓️] order: -10 /* <- Note: Putting before other commands */
28473
+ examples: [`BOOK VERSION ${BOOK_LANGUAGE_VERSION}`, `BOOK ${BOOK_LANGUAGE_VERSION}`],
28431
28474
  /**
28432
- * Parses the SECTION command
28475
+ * Parses the BOOK_VERSION command
28433
28476
  */
28434
28477
  parse(input) {
28435
- let { normalized } = input;
28436
- normalized = normalized.split('SAMPLE').join('EXAMPLE');
28437
- normalized = normalized.split('EXECUTE_').join('');
28438
- normalized = normalized.split('DIALOGUE').join('DIALOG');
28439
- const taskTypes = SectionTypes.filter((sectionType) => normalized.includes(sectionType.split('_TASK').join('')));
28440
- if (taskTypes.length !== 1) {
28441
- throw new ParseError(spaceTrim$1((block) => `
28442
- Unknown section type "${normalized}"
28443
-
28444
- Supported section types are:
28445
- ${block(SectionTypes.join(', '))}
28446
- `));
28478
+ const { args } = input;
28479
+ const bookVersion = args.pop();
28480
+ if (bookVersion === undefined) {
28481
+ throw new ParseError(`Version is required`);
28482
+ }
28483
+ if (!isValidPromptbookVersion(bookVersion)) {
28484
+ throw new ParseError(`Invalid Promptbook version "${bookVersion}"`);
28485
+ }
28486
+ if (args.length > 0 && !(((args.length === 1 && args[0]) || '').toUpperCase() === 'VERSION')) {
28487
+ throw new ParseError(`Can not have more than one Promptbook version`);
28447
28488
  }
28448
- const taskType = taskTypes[0];
28449
28489
  return {
28450
- type: 'SECTION',
28451
- taskType,
28490
+ type: 'BOOK_VERSION',
28491
+ bookVersion: bookVersion,
28452
28492
  };
28453
28493
  },
28454
28494
  /**
28455
- * Apply the SECTION command to the `pipelineJson`
28495
+ * Apply the BOOK_VERSION command to the `pipelineJson`
28456
28496
  *
28457
- * Note: `$` is used to indicate that this function mutates given `taskJson`
28497
+ * Note: `$` is used to indicate that this function mutates given `pipelineJson`
28458
28498
  */
28459
- $applyToTaskJson(command, $taskJson, $pipelineJson) {
28460
- if ($taskJson.isSectionTypeSet === true) {
28461
- throw new ParseError(spaceTrim$1(`
28462
- Section type is already defined in the section.
28463
- It can be defined only once.
28464
- `));
28465
- }
28466
- $taskJson.isSectionTypeSet = true;
28467
- // TODO: [🍧][💩] Rearrange better - but at bottom and unwrap from function
28468
- const expectResultingParameterName = () => {
28469
- if ($taskJson.resultingParameterName) {
28470
- return;
28471
- }
28472
- throw new ParseError(`Task section and example section must end with return statement -> {parameterName}`);
28473
- };
28474
- if ($taskJson.content === undefined) {
28475
- throw new UnexpectedError(`Content is missing in the taskJson - probably commands are applied in wrong order`);
28476
- }
28477
- if (command.taskType === 'EXAMPLE') {
28478
- expectResultingParameterName();
28479
- const parameter = $pipelineJson.parameters.find((param) => param.name === $taskJson.resultingParameterName);
28480
- if (parameter === undefined) {
28481
- // TODO: !!6 Change to logic error for higher level abstraction of chatbot to work
28482
- throw new ParseError(`Parameter \`{${$taskJson.resultingParameterName}}\` is not defined so can not define example value of it`);
28483
- }
28484
- parameter.exampleValues = parameter.exampleValues || [];
28485
- parameter.exampleValues.push($taskJson.content);
28486
- $taskJson.isTask = false;
28487
- return;
28488
- }
28489
- if (command.taskType === 'KNOWLEDGE') {
28490
- knowledgeCommandParser.$applyToPipelineJson({
28491
- type: 'KNOWLEDGE',
28492
- knowledgeSourceContent: $taskJson.content, // <- TODO: [🐝][main] !!3 Work with KNOWLEDGE which not referring to the source file or website, but its content itself
28493
- }, $pipelineJson);
28494
- $taskJson.isTask = false;
28495
- return;
28496
- }
28497
- if (command.taskType === 'ACTION') {
28498
- console.error(new NotYetImplementedError('Actions are not implemented yet'));
28499
- $taskJson.isTask = false;
28500
- return;
28501
- }
28502
- if (command.taskType === 'INSTRUMENT') {
28503
- console.error(new NotYetImplementedError('Instruments are not implemented yet'));
28504
- $taskJson.isTask = false;
28505
- return;
28506
- }
28507
- expectResultingParameterName();
28508
- $taskJson.taskType = command.taskType;
28509
- $taskJson.isTask = true;
28499
+ $applyToPipelineJson(command, $pipelineJson) {
28500
+ // TODO: Warn if the version is overridden
28501
+ $pipelineJson.bookVersion = command.bookVersion;
28510
28502
  },
28511
28503
  /**
28512
- * Converts the SECTION command back to string
28504
+ * Converts the BOOK_VERSION command back to string
28513
28505
  *
28514
28506
  * Note: This is used in `pipelineJsonToString` utility
28515
28507
  */
@@ -28517,218 +28509,36 @@ const sectionCommandParser = {
28517
28509
  return `---`; // <- TODO: [🛋] Implement
28518
28510
  },
28519
28511
  /**
28520
- * Reads the SECTION command from the `TaskJson`
28512
+ * Reads the BOOK_VERSION command from the `PipelineJson`
28521
28513
  *
28522
28514
  * Note: This is used in `pipelineJsonToString` utility
28523
28515
  */
28524
- takeFromTaskJson($taskJson) {
28516
+ takeFromPipelineJson(pipelineJson) {
28525
28517
  throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
28526
28518
  },
28527
28519
  };
28520
+
28528
28521
  /**
28529
- * Note: [⛱] There are two types of KNOWLEDGE, ACTION and INSTRUMENT commands:
28530
- * 1) There are commands `KNOWLEDGE`, `ACTION` and `INSTRUMENT` used in the pipeline head, they just define the knowledge, action or instrument as single line after the command
28531
- * - KNOWLEDGE Look at https://en.wikipedia.org/wiki/Artificial_intelligence
28532
- * 2) `KNOWLEDGE SECTION` which has short form `KNOWLEDGE` is used in the sectiom, does not refer the line itself, but the content of the section block
28533
- * - KNOWLEDGE SECTION
28522
+ * Units of text measurement
28534
28523
  *
28535
- * ```
28536
- * Look at https://en.wikipedia.org/wiki/Artificial_intelligence
28537
- * ```
28524
+ * @see https://github.com/webgptorg/promptbook/discussions/30
28525
+ *
28526
+ * @public exported from `@promptbook/core`
28538
28527
  */
28528
+ const EXPECTATION_UNITS = ['CHARACTERS', 'WORDS', 'SENTENCES', 'LINES', 'PARAGRAPHS', 'PAGES'];
28529
+ // TODO: [💝] Unite object for expecting amount and format - remove format
28539
28530
 
28540
28531
  /**
28541
- * Parses the boilerplate command
28532
+ * Function parseNumber will parse number from string
28542
28533
  *
28543
- * Note: @@ This command is used as boilerplate for new commands - it should NOT be used in any `.book` file
28534
+ * Note: [🔂] This function is idempotent.
28535
+ * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
28536
+ * Note: it also works only with decimal numbers
28544
28537
  *
28545
- * @see `documentationUrl` for more details
28538
+ * @returns parsed number
28539
+ * @throws {ParseError} if the value is not a number
28546
28540
  *
28547
- * @private within the commands folder
28548
- */
28549
- const boilerplateCommandParser = {
28550
- /**
28551
- * Name of the command
28552
- */
28553
- name: 'BOILERPLATE',
28554
- /**
28555
- * Aliases for the BOILERPLATE command
28556
- */
28557
- aliasNames: ['BP'],
28558
- /**
28559
- * BOILERPLATE command can be used in:
28560
- */
28561
- isUsedInPipelineHead: true,
28562
- isUsedInPipelineTask: true,
28563
- /**
28564
- * Description of the BOILERPLATE command
28565
- */
28566
- description: `@@`,
28567
- /**
28568
- * Link to documentation
28569
- */
28570
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/@@',
28571
- /**
28572
- * Example usages of the BOILERPLATE command
28573
- */
28574
- examples: ['BOILERPLATE foo', 'BOILERPLATE bar', 'BP foo', 'BP bar'],
28575
- /**
28576
- * Parses the BOILERPLATE command
28577
- */
28578
- parse(input) {
28579
- const { args } = input;
28580
- if (args.length !== 1) {
28581
- throw new ParseError(`BOILERPLATE command requires exactly one argument`);
28582
- }
28583
- const value = args[0].toLowerCase();
28584
- if (value.includes('brr')) {
28585
- throw new ParseError(`BOILERPLATE value can not contain brr`);
28586
- }
28587
- return {
28588
- type: 'BOILERPLATE',
28589
- value,
28590
- };
28591
- },
28592
- /**
28593
- * Apply the BOILERPLATE command to the `pipelineJson`
28594
- *
28595
- * Note: `$` is used to indicate that this function mutates given `pipelineJson`
28596
- */
28597
- $applyToPipelineJson(command, $pipelineJson) {
28598
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28599
- },
28600
- /**
28601
- * Apply the BOILERPLATE command to the `pipelineJson`
28602
- *
28603
- * Note: `$` is used to indicate that this function mutates given `taskJson`
28604
- */
28605
- $applyToTaskJson(command, $taskJson, $pipelineJson) {
28606
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28607
- },
28608
- /**
28609
- * Converts the BOILERPLATE command back to string
28610
- *
28611
- * Note: This is used in `pipelineJsonToString` utility
28612
- */
28613
- stringify(command) {
28614
- return `---`; // <- TODO: [🛋] Implement
28615
- },
28616
- /**
28617
- * Reads the BOILERPLATE command from the `PipelineJson`
28618
- *
28619
- * Note: This is used in `pipelineJsonToString` utility
28620
- */
28621
- takeFromPipelineJson(pipelineJson) {
28622
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28623
- },
28624
- /**
28625
- * Reads the BOILERPLATE command from the `TaskJson`
28626
- *
28627
- * Note: This is used in `pipelineJsonToString` utility
28628
- */
28629
- takeFromTaskJson($taskJson) {
28630
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
28631
- },
28632
- };
28633
-
28634
- /**
28635
- * Parses the BOOK_VERSION command
28636
- *
28637
- * @see `documentationUrl` for more details
28638
- *
28639
- * @public exported from `@promptbook/editable`
28640
- */
28641
- const bookVersionCommandParser = {
28642
- /**
28643
- * Name of the command
28644
- */
28645
- name: 'BOOK_VERSION',
28646
- aliasNames: ['PTBK_VERSION', 'PROMPTBOOK_VERSION', 'BOOK'],
28647
- /**
28648
- * BOILERPLATE command can be used in:
28649
- */
28650
- isUsedInPipelineHead: true,
28651
- isUsedInPipelineTask: false,
28652
- /**
28653
- * Description of the BOOK_VERSION command
28654
- */
28655
- description: `Which version of the Book language is the .book.md using`,
28656
- /**
28657
- * Link to documentation
28658
- */
28659
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/69',
28660
- /**
28661
- * Example usages of the BOOK_VERSION command
28662
- */
28663
- examples: [`BOOK VERSION ${BOOK_LANGUAGE_VERSION}`, `BOOK ${BOOK_LANGUAGE_VERSION}`],
28664
- /**
28665
- * Parses the BOOK_VERSION command
28666
- */
28667
- parse(input) {
28668
- const { args } = input;
28669
- const bookVersion = args.pop();
28670
- if (bookVersion === undefined) {
28671
- throw new ParseError(`Version is required`);
28672
- }
28673
- if (!isValidPromptbookVersion(bookVersion)) {
28674
- throw new ParseError(`Invalid Promptbook version "${bookVersion}"`);
28675
- }
28676
- if (args.length > 0 && !(((args.length === 1 && args[0]) || '').toUpperCase() === 'VERSION')) {
28677
- throw new ParseError(`Can not have more than one Promptbook version`);
28678
- }
28679
- return {
28680
- type: 'BOOK_VERSION',
28681
- bookVersion: bookVersion,
28682
- };
28683
- },
28684
- /**
28685
- * Apply the BOOK_VERSION command to the `pipelineJson`
28686
- *
28687
- * Note: `$` is used to indicate that this function mutates given `pipelineJson`
28688
- */
28689
- $applyToPipelineJson(command, $pipelineJson) {
28690
- // TODO: Warn if the version is overridden
28691
- $pipelineJson.bookVersion = command.bookVersion;
28692
- },
28693
- /**
28694
- * Converts the BOOK_VERSION command back to string
28695
- *
28696
- * Note: This is used in `pipelineJsonToString` utility
28697
- */
28698
- stringify(command) {
28699
- return `---`; // <- TODO: [🛋] Implement
28700
- },
28701
- /**
28702
- * Reads the BOOK_VERSION command from the `PipelineJson`
28703
- *
28704
- * Note: This is used in `pipelineJsonToString` utility
28705
- */
28706
- takeFromPipelineJson(pipelineJson) {
28707
- throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
28708
- },
28709
- };
28710
-
28711
- /**
28712
- * Units of text measurement
28713
- *
28714
- * @see https://github.com/webgptorg/promptbook/discussions/30
28715
- *
28716
- * @public exported from `@promptbook/core`
28717
- */
28718
- const EXPECTATION_UNITS = ['CHARACTERS', 'WORDS', 'SENTENCES', 'LINES', 'PARAGRAPHS', 'PAGES'];
28719
- // TODO: [💝] Unite object for expecting amount and format - remove format
28720
-
28721
- /**
28722
- * Function parseNumber will parse number from string
28723
- *
28724
- * Note: [🔂] This function is idempotent.
28725
- * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
28726
- * Note: it also works only with decimal numbers
28727
- *
28728
- * @returns parsed number
28729
- * @throws {ParseError} if the value is not a number
28730
- *
28731
- * @public exported from `@promptbook/utils`
28541
+ * @public exported from `@promptbook/utils`
28732
28542
  */
28733
28543
  function parseNumber(value) {
28734
28544
  if (value === null || value === undefined) {
@@ -29713,6 +29523,99 @@ const jokerCommandParser = {
29713
29523
  },
29714
29524
  };
29715
29525
 
29526
+ /**
29527
+ * Parses the knowledge command
29528
+ *
29529
+ * @see `documentationUrl` for more details
29530
+ *
29531
+ * @public exported from `@promptbook/editable`
29532
+ */
29533
+ const knowledgeCommandParser = {
29534
+ /**
29535
+ * Name of the command
29536
+ */
29537
+ name: 'KNOWLEDGE',
29538
+ /**
29539
+ * BOILERPLATE command can be used in:
29540
+ */
29541
+ isUsedInPipelineHead: true,
29542
+ isUsedInPipelineTask: false,
29543
+ /**
29544
+ * Description of the KNOWLEDGE command
29545
+ */
29546
+ description: `Tells promptbook which external knowledge to use`,
29547
+ /**
29548
+ * Link to documentation
29549
+ */
29550
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/41',
29551
+ /**
29552
+ * Example usages of the KNOWLEDGE command
29553
+ */
29554
+ examples: [
29555
+ 'KNOWLEDGE https://www.pavolhejny.com/',
29556
+ 'KNOWLEDGE ./hejny-cv.txt',
29557
+ 'KNOWLEDGE ./hejny-cv.md',
29558
+ 'KNOWLEDGE ./hejny-cv.pdf',
29559
+ 'KNOWLEDGE ./hejny-cv.docx',
29560
+ // <- TODO: [😿] Allow ONLY files scoped in the (sub)directory NOT ../ and test it
29561
+ ],
29562
+ /**
29563
+ * Parses the KNOWLEDGE command
29564
+ */
29565
+ parse(input) {
29566
+ const { args } = input;
29567
+ const knowledgeSourceContent = spaceTrim$1(args[0] || '');
29568
+ if (knowledgeSourceContent === '') {
29569
+ throw new ParseError(`Source is not defined`);
29570
+ }
29571
+ // TODO: [main] !!4 Following checks should be applied every link in the `sourceContent`
29572
+ if (knowledgeSourceContent.startsWith('http://')) {
29573
+ throw new ParseError(`Source is not secure`);
29574
+ }
29575
+ if (!(isValidFilePath(knowledgeSourceContent) || isValidUrl(knowledgeSourceContent))) {
29576
+ throw new ParseError(`Source not valid`);
29577
+ }
29578
+ if (knowledgeSourceContent.startsWith('../') ||
29579
+ knowledgeSourceContent.startsWith('/') ||
29580
+ /^[A-Z]:[\\/]+/i.test(knowledgeSourceContent)) {
29581
+ throw new ParseError(`Source cannot be outside of the .book.md folder`);
29582
+ }
29583
+ return {
29584
+ type: 'KNOWLEDGE',
29585
+ knowledgeSourceContent,
29586
+ };
29587
+ },
29588
+ /**
29589
+ * Apply the KNOWLEDGE command to the `pipelineJson`
29590
+ *
29591
+ * Note: `$` is used to indicate that this function mutates given `pipelineJson`
29592
+ */
29593
+ $applyToPipelineJson(command, $pipelineJson) {
29594
+ const { knowledgeSourceContent } = command;
29595
+ $pipelineJson.knowledgeSources.push({
29596
+ name: knowledgeSourceContentToName(knowledgeSourceContent),
29597
+ knowledgeSourceContent,
29598
+ });
29599
+ },
29600
+ /**
29601
+ * Converts the KNOWLEDGE command back to string
29602
+ *
29603
+ * Note: This is used in `pipelineJsonToString` utility
29604
+ */
29605
+ stringify(command) {
29606
+ return `---`; // <- TODO: [🛋] Implement
29607
+ },
29608
+ /**
29609
+ * Reads the KNOWLEDGE command from the `PipelineJson`
29610
+ *
29611
+ * Note: This is used in `pipelineJsonToString` utility
29612
+ */
29613
+ takeFromPipelineJson(pipelineJson) {
29614
+ throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
29615
+ },
29616
+ };
29617
+ // Note: [⛱] There are two types of KNOWLEDGE commands *...(read more in [⛱])*
29618
+
29716
29619
  /**
29717
29620
  * Constant for model variants.
29718
29621
  *
@@ -30247,7 +30150,234 @@ const postprocessCommandParser = {
30247
30150
  };
30248
30151
 
30249
30152
  /**
30250
- * Parses the url command
30153
+ * All available task types
30154
+ *
30155
+ * There is is distinction between task types and section types
30156
+ * - Every section in markdown has its SectionType
30157
+ * - Some sections are tasks but other can be non-task sections
30158
+ *
30159
+ * @public exported from `@promptbook/core`
30160
+ */
30161
+ const TaskTypes = [
30162
+ 'PROMPT',
30163
+ 'SIMPLE',
30164
+ 'SCRIPT',
30165
+ 'DIALOG',
30166
+ // <- [🅱]
30167
+ ];
30168
+
30169
+ /**
30170
+ * All available sections which are not tasks
30171
+ *
30172
+ * @public exported from `@promptbook/core`
30173
+ */
30174
+ const NonTaskSectionTypes = ['EXAMPLE', 'KNOWLEDGE', 'INSTRUMENT', 'ACTION'];
30175
+ /**
30176
+ * All available section types
30177
+ *
30178
+ * There is is distinction between task types and section types
30179
+ * - Every section in markdown has its SectionType
30180
+ * - Some sections are tasks but other can be non-task sections
30181
+ *
30182
+ * @public exported from `@promptbook/core`
30183
+ */
30184
+ const SectionTypes = [
30185
+ ...TaskTypes.map((TaskType) => `${TaskType}_TASK`),
30186
+ ...NonTaskSectionTypes,
30187
+ ];
30188
+
30189
+ /**
30190
+ * Parses the section command
30191
+ *
30192
+ * @see `documentationUrl` for more details
30193
+ *
30194
+ * @public exported from `@promptbook/editable`
30195
+ */
30196
+ const sectionCommandParser = {
30197
+ /**
30198
+ * Name of the command
30199
+ */
30200
+ name: 'SECTION',
30201
+ /**
30202
+ * Aliases for the SECTION command
30203
+ */
30204
+ aliasNames: [
30205
+ 'PROMPT',
30206
+ 'SIMPLE',
30207
+ 'SCRIPT',
30208
+ 'DIALOG',
30209
+ 'SAMPLE',
30210
+ 'EXAMPLE',
30211
+ 'KNOWLEDGE',
30212
+ 'INSTRUMENT',
30213
+ 'ACTION', // <- Note: [⛱]
30214
+ ],
30215
+ /**
30216
+ * Aliases for the SECTION command
30217
+ */
30218
+ deprecatedNames: ['TEMPLATE', 'BLOCK', 'EXECUTE'],
30219
+ /**
30220
+ * BOILERPLATE command can be used in:
30221
+ */
30222
+ isUsedInPipelineHead: false,
30223
+ isUsedInPipelineTask: true,
30224
+ /**
30225
+ * Description of the SECTION command
30226
+ */
30227
+ description: `Defines the purpose of the markdown section - if its a task and which type or something else`,
30228
+ /**
30229
+ * Link to documentation
30230
+ */
30231
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/64',
30232
+ /**
30233
+ * Example usages of the SECTION command
30234
+ */
30235
+ examples: [
30236
+ // Short form:
30237
+ 'PROMPT',
30238
+ 'SIMPLE',
30239
+ 'SCRIPT',
30240
+ 'DIALOG',
30241
+ // <- [🅱]
30242
+ 'EXAMPLE',
30243
+ 'KNOWLEDGE',
30244
+ 'INSTRUMENT',
30245
+ 'ACTION',
30246
+ // -----------------
30247
+ // Recommended (reversed) form:
30248
+ 'PROMPT SECTION',
30249
+ 'SIMPLE SECTION',
30250
+ 'SCRIPT SECTION',
30251
+ 'DIALOG SECTION',
30252
+ // <- [🅱]
30253
+ 'EXAMPLE SECTION',
30254
+ 'KNOWLEDGE SECTION',
30255
+ 'INSTRUMENT SECTION',
30256
+ 'ACTION SECTION',
30257
+ // -----------------
30258
+ // Standard form:
30259
+ 'SECTION PROMPT',
30260
+ 'SECTION SIMPLE',
30261
+ 'SECTION SCRIPT',
30262
+ 'SECTION DIALOG',
30263
+ // <- [🅱]
30264
+ 'SECTION EXAMPLE',
30265
+ 'SECTION KNOWLEDGE',
30266
+ 'SECTION INSTRUMENT',
30267
+ 'SECTION ACTION',
30268
+ ],
30269
+ // TODO: [♓️] order: -10 /* <- Note: Putting before other commands */
30270
+ /**
30271
+ * Parses the SECTION command
30272
+ */
30273
+ parse(input) {
30274
+ let { normalized } = input;
30275
+ normalized = normalized.split('SAMPLE').join('EXAMPLE');
30276
+ normalized = normalized.split('EXECUTE_').join('');
30277
+ normalized = normalized.split('DIALOGUE').join('DIALOG');
30278
+ const taskTypes = SectionTypes.filter((sectionType) => normalized.includes(sectionType.split('_TASK').join('')));
30279
+ if (taskTypes.length !== 1) {
30280
+ throw new ParseError(spaceTrim$1((block) => `
30281
+ Unknown section type "${normalized}"
30282
+
30283
+ Supported section types are:
30284
+ ${block(SectionTypes.join(', '))}
30285
+ `));
30286
+ }
30287
+ const taskType = taskTypes[0];
30288
+ return {
30289
+ type: 'SECTION',
30290
+ taskType,
30291
+ };
30292
+ },
30293
+ /**
30294
+ * Apply the SECTION command to the `pipelineJson`
30295
+ *
30296
+ * Note: `$` is used to indicate that this function mutates given `taskJson`
30297
+ */
30298
+ $applyToTaskJson(command, $taskJson, $pipelineJson) {
30299
+ if ($taskJson.isSectionTypeSet === true) {
30300
+ throw new ParseError(spaceTrim$1(`
30301
+ Section type is already defined in the section.
30302
+ It can be defined only once.
30303
+ `));
30304
+ }
30305
+ $taskJson.isSectionTypeSet = true;
30306
+ // TODO: [🍧][💩] Rearrange better - but at bottom and unwrap from function
30307
+ const expectResultingParameterName = () => {
30308
+ if ($taskJson.resultingParameterName) {
30309
+ return;
30310
+ }
30311
+ throw new ParseError(`Task section and example section must end with return statement -> {parameterName}`);
30312
+ };
30313
+ if ($taskJson.content === undefined) {
30314
+ throw new UnexpectedError(`Content is missing in the taskJson - probably commands are applied in wrong order`);
30315
+ }
30316
+ if (command.taskType === 'EXAMPLE') {
30317
+ expectResultingParameterName();
30318
+ const parameter = $pipelineJson.parameters.find((param) => param.name === $taskJson.resultingParameterName);
30319
+ if (parameter === undefined) {
30320
+ // TODO: !!6 Change to logic error for higher level abstraction of chatbot to work
30321
+ throw new ParseError(`Parameter \`{${$taskJson.resultingParameterName}}\` is not defined so can not define example value of it`);
30322
+ }
30323
+ parameter.exampleValues = parameter.exampleValues || [];
30324
+ parameter.exampleValues.push($taskJson.content);
30325
+ $taskJson.isTask = false;
30326
+ return;
30327
+ }
30328
+ if (command.taskType === 'KNOWLEDGE') {
30329
+ knowledgeCommandParser.$applyToPipelineJson({
30330
+ type: 'KNOWLEDGE',
30331
+ knowledgeSourceContent: $taskJson.content, // <- TODO: [🐝][main] !!3 Work with KNOWLEDGE which not referring to the source file or website, but its content itself
30332
+ }, $pipelineJson);
30333
+ $taskJson.isTask = false;
30334
+ return;
30335
+ }
30336
+ if (command.taskType === 'ACTION') {
30337
+ console.error(new NotYetImplementedError('Actions are not implemented yet'));
30338
+ $taskJson.isTask = false;
30339
+ return;
30340
+ }
30341
+ if (command.taskType === 'INSTRUMENT') {
30342
+ console.error(new NotYetImplementedError('Instruments are not implemented yet'));
30343
+ $taskJson.isTask = false;
30344
+ return;
30345
+ }
30346
+ expectResultingParameterName();
30347
+ $taskJson.taskType = command.taskType;
30348
+ $taskJson.isTask = true;
30349
+ },
30350
+ /**
30351
+ * Converts the SECTION command back to string
30352
+ *
30353
+ * Note: This is used in `pipelineJsonToString` utility
30354
+ */
30355
+ stringify(command) {
30356
+ return `---`; // <- TODO: [🛋] Implement
30357
+ },
30358
+ /**
30359
+ * Reads the SECTION command from the `TaskJson`
30360
+ *
30361
+ * Note: This is used in `pipelineJsonToString` utility
30362
+ */
30363
+ takeFromTaskJson($taskJson) {
30364
+ throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
30365
+ },
30366
+ };
30367
+ /**
30368
+ * Note: [⛱] There are two types of KNOWLEDGE, ACTION and INSTRUMENT commands:
30369
+ * 1) There are commands `KNOWLEDGE`, `ACTION` and `INSTRUMENT` used in the pipeline head, they just define the knowledge, action or instrument as single line after the command
30370
+ * - KNOWLEDGE Look at https://en.wikipedia.org/wiki/Artificial_intelligence
30371
+ * 2) `KNOWLEDGE SECTION` which has short form `KNOWLEDGE` is used in the sectiom, does not refer the line itself, but the content of the section block
30372
+ * - KNOWLEDGE SECTION
30373
+ *
30374
+ * ```
30375
+ * Look at https://en.wikipedia.org/wiki/Artificial_intelligence
30376
+ * ```
30377
+ */
30378
+
30379
+ /**
30380
+ * Parses the url command
30251
30381
  *
30252
30382
  * @see `documentationUrl` for more details
30253
30383
  *
@@ -30700,29 +30830,265 @@ function parseCommandVariant(input) {
30700
30830
  }
30701
30831
 
30702
30832
  /**
30703
- * Extracts the interface (input and output parameters) from a pipeline.
30833
+ * Utility function to extract all list items from markdown
30704
30834
  *
30705
- * @deprecated https://github.com/webgptorg/promptbook/pull/186
30706
- * @see https://github.com/webgptorg/promptbook/discussions/171
30835
+ * Note: It works with both ul and ol
30836
+ * Note: It omits list items in code blocks
30837
+ * Note: It flattens nested lists
30838
+ * Note: It can not work with html syntax and comments
30707
30839
  *
30708
- * @public exported from `@promptbook/core`
30840
+ * @param markdown any valid markdown
30841
+ * @returns An array of strings, each representing an individual list item found in the markdown
30842
+ *
30843
+ * @public exported from `@promptbook/markdown-utils`
30709
30844
  */
30710
- function getPipelineInterface(pipeline) {
30711
- const pipelineInterface = {
30712
- inputParameters: [],
30713
- outputParameters: [],
30714
- };
30715
- for (const parameter of pipeline.parameters) {
30716
- const { isInput, isOutput } = parameter;
30717
- if (isInput) {
30718
- pipelineInterface.inputParameters.push(deepClone(parameter));
30719
- }
30720
- if (isOutput) {
30721
- pipelineInterface.outputParameters.push(deepClone(parameter));
30722
- }
30723
- }
30724
- for (const key of ['inputParameters', 'outputParameters']) {
30725
- pipelineInterface[key].sort(({ name: name1 }, { name: name2 }) => name1.localeCompare(name2));
30845
+ function extractAllListItemsFromMarkdown(markdown) {
30846
+ const lines = markdown.split(/\r?\n/);
30847
+ const listItems = [];
30848
+ let isInCodeBlock = false;
30849
+ for (const line of lines) {
30850
+ const trimmedLine = line.trim();
30851
+ if (trimmedLine.startsWith('```')) {
30852
+ isInCodeBlock = !isInCodeBlock;
30853
+ }
30854
+ if (!isInCodeBlock && (trimmedLine.startsWith('-') || trimmedLine.match(/^\d+\./))) {
30855
+ const listItem = trimmedLine.replace(/^-|\d+\./, '').trim();
30856
+ listItems.push(listItem);
30857
+ }
30858
+ }
30859
+ return listItems;
30860
+ }
30861
+
30862
+ /**
30863
+ * Builds a short file/url identification block for parse errors.
30864
+ *
30865
+ * @private internal utility of `parsePipeline`
30866
+ */
30867
+ function getPipelineIdentification($pipelineJson) {
30868
+ // Note: This is a 😐 implementation of [🚞]
30869
+ const pipelineIdentificationParts = [];
30870
+ if ($pipelineJson.sourceFile !== undefined) {
30871
+ pipelineIdentificationParts.push(`File: ${$pipelineJson.sourceFile}`);
30872
+ }
30873
+ if ($pipelineJson.pipelineUrl !== undefined) {
30874
+ pipelineIdentificationParts.push(`Url: ${$pipelineJson.pipelineUrl}`);
30875
+ }
30876
+ return pipelineIdentificationParts.join('\n');
30877
+ }
30878
+
30879
+ /**
30880
+ * Merges one parameter declaration into the mutable pipeline parameter list.
30881
+ *
30882
+ * @private internal utility of `parsePipeline`
30883
+ */
30884
+ function defineParameter($pipelineJson, parameterCommand) {
30885
+ const { parameterName, parameterDescription, isInput, isOutput } = parameterCommand;
30886
+ if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
30887
+ throw new ParseError(spaceTrim$1((block) => `
30888
+ Parameter name {${parameterName}} is reserved and cannot be used as resulting parameter name
30889
+
30890
+ ${block(getPipelineIdentification($pipelineJson))}
30891
+ `) /* <- TODO: [🚞] */);
30892
+ }
30893
+ const existingParameter = $pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
30894
+ if (existingParameter &&
30895
+ existingParameter.description &&
30896
+ existingParameter.description !== parameterDescription &&
30897
+ parameterDescription) {
30898
+ throw new ParseError(spaceTrim$1((block) => `
30899
+ Parameter \`{${parameterName}}\` is defined multiple times with different description:
30900
+
30901
+ ${block(getPipelineIdentification($pipelineJson))}
30902
+
30903
+ First definition:
30904
+ ${block(existingParameter.description || '[undefined]')}
30905
+
30906
+ Second definition:
30907
+ ${block(parameterDescription || '[undefined]')}
30908
+ `));
30909
+ }
30910
+ if (existingParameter) {
30911
+ if (parameterDescription) {
30912
+ existingParameter.description = parameterDescription;
30913
+ }
30914
+ existingParameter.isInput = existingParameter.isInput || isInput;
30915
+ existingParameter.isOutput = existingParameter.isOutput || isOutput;
30916
+ return;
30917
+ }
30918
+ $pipelineJson.parameters.push({
30919
+ name: parameterName,
30920
+ description: parameterDescription || undefined,
30921
+ isInput,
30922
+ isOutput,
30923
+ });
30924
+ }
30925
+
30926
+ /**
30927
+ * Removes fenced code blocks when deriving human-readable section descriptions.
30928
+ *
30929
+ * @private internal utility of `extractPipelineDescription`
30930
+ */
30931
+ const DESCRIPTION_CODE_BLOCK_REGEXP = /^```.*^```/gms;
30932
+ /**
30933
+ * Removes blockquote lines when deriving human-readable section descriptions.
30934
+ *
30935
+ * @private internal utility of `extractPipelineDescription`
30936
+ */
30937
+ const DESCRIPTION_BLOCKQUOTE_REGEXP = /^>.*$/gm;
30938
+ /**
30939
+ * Removes list items and return statements when deriving human-readable section descriptions.
30940
+ *
30941
+ * @private internal utility of `extractPipelineDescription`
30942
+ */
30943
+ const DESCRIPTION_LIST_ITEM_REGEXP = /^(?:(?:-)|(?:\d\))|(?:`?->))\s+.*$/gm;
30944
+ /**
30945
+ * Extracts the plain-text description from a head or task section body.
30946
+ *
30947
+ * @private internal utility of `parsePipeline`
30948
+ */
30949
+ function extractPipelineDescription(sectionContent) {
30950
+ let description = sectionContent;
30951
+ description = description.split(DESCRIPTION_CODE_BLOCK_REGEXP).join('');
30952
+ description = description.split(DESCRIPTION_BLOCKQUOTE_REGEXP).join('');
30953
+ description = description.split(DESCRIPTION_LIST_ITEM_REGEXP).join('');
30954
+ description = spaceTrim$1(description);
30955
+ if (description === '') {
30956
+ return undefined;
30957
+ }
30958
+ return description;
30959
+ }
30960
+
30961
+ /**
30962
+ * Applies the pipeline head title, description, and head-level commands.
30963
+ *
30964
+ * @private internal utility of `parsePipeline`
30965
+ */
30966
+ function applyPipelineHead(pipelineHead, $pipelineJson) {
30967
+ $pipelineJson.title = pipelineHead.title;
30968
+ $pipelineJson.description = extractPipelineDescription(pipelineHead.content);
30969
+ for (const listItem of extractAllListItemsFromMarkdown(pipelineHead.content)) {
30970
+ applyPipelineHeadCommand(listItem, $pipelineJson);
30971
+ }
30972
+ }
30973
+ /**
30974
+ * Parses and applies one command declared in the pipeline head.
30975
+ *
30976
+ * @private internal utility of `applyPipelineHead`
30977
+ */
30978
+ function applyPipelineHeadCommand(listItem, $pipelineJson) {
30979
+ const command = parseCommand(listItem, 'PIPELINE_HEAD');
30980
+ const commandParser = getParserForCommand(command);
30981
+ if (commandParser.isUsedInPipelineHead !== true /* <- Note: [🦦][4] */) {
30982
+ throw new ParseError(spaceTrim$1((block) => `
30983
+ Command \`${command.type}\` is not allowed in the head of the pipeline ONLY at the pipeline task
30984
+
30985
+ ${block(getPipelineIdentification($pipelineJson))}
30986
+ `)); // <- TODO: [🚞]
30987
+ }
30988
+ try {
30989
+ commandParser.$applyToPipelineJson(command, $pipelineJson);
30990
+ // <- Note: [🦦] Its strange that this assertion must be here, [🦦][4] should do this assertion implicitly
30991
+ }
30992
+ catch (error) {
30993
+ if (!(error instanceof ParseError)) {
30994
+ throw error;
30995
+ }
30996
+ throw new ParseError(spaceTrim$1((block) => `
30997
+ Command ${command.type} failed to apply to the pipeline
30998
+
30999
+ The error:
31000
+ ${block(error.message)}
31001
+
31002
+ Raw command:
31003
+ - ${listItem}
31004
+
31005
+ Usage of ${command.type}:
31006
+ ${block(commandParser.examples.map((example) => `- ${example}`).join('\n'))}
31007
+
31008
+ ${block(getPipelineIdentification($pipelineJson))}
31009
+ `)); // <- TODO: [🚞]
31010
+ }
31011
+ if (command.type === 'PARAMETER') {
31012
+ defineParameter($pipelineJson, command);
31013
+ // <- Note: [🍣]
31014
+ }
31015
+ }
31016
+
31017
+ /**
31018
+ * Creates the mutable pipeline JSON structure used throughout parsing.
31019
+ *
31020
+ * @private internal utility of `parsePipeline`
31021
+ */
31022
+ function createInitialPipelineJson(pipelineString) {
31023
+ return {
31024
+ title: DEFAULT_BOOK_TITLE,
31025
+ parameters: [],
31026
+ tasks: [],
31027
+ knowledgeSources: [],
31028
+ knowledgePieces: [],
31029
+ personas: [],
31030
+ preparations: [],
31031
+ sources: [
31032
+ {
31033
+ type: 'BOOK',
31034
+ path: null,
31035
+ // <- TODO: !!6 Pass here path of the file
31036
+ content: pipelineString,
31037
+ },
31038
+ ],
31039
+ };
31040
+ }
31041
+
31042
+ /**
31043
+ * Creates stable unique task names for duplicate section titles.
31044
+ *
31045
+ * @private internal utility of `parsePipeline`
31046
+ */
31047
+ function createUniqueSectionNameResolver(pipelineSections) {
31048
+ const sectionCounts = {};
31049
+ for (const pipelineSection of pipelineSections) {
31050
+ const sectionName = titleToName(pipelineSection.title);
31051
+ if (sectionCounts[sectionName] === undefined) {
31052
+ sectionCounts[sectionName] = { count: 0, currentIndex: 0 };
31053
+ }
31054
+ sectionCounts[sectionName].count++;
31055
+ }
31056
+ return (title) => {
31057
+ const sectionName = titleToName(title);
31058
+ const sectionCount = sectionCounts[sectionName];
31059
+ if (sectionCount.count === 1) {
31060
+ return sectionName;
31061
+ }
31062
+ const nameWithSuffix = `${sectionName}-${sectionCount.currentIndex}`;
31063
+ sectionCount.currentIndex++;
31064
+ return nameWithSuffix;
31065
+ };
31066
+ }
31067
+
31068
+ /**
31069
+ * Extracts the interface (input and output parameters) from a pipeline.
31070
+ *
31071
+ * @deprecated https://github.com/webgptorg/promptbook/pull/186
31072
+ * @see https://github.com/webgptorg/promptbook/discussions/171
31073
+ *
31074
+ * @public exported from `@promptbook/core`
31075
+ */
31076
+ function getPipelineInterface(pipeline) {
31077
+ const pipelineInterface = {
31078
+ inputParameters: [],
31079
+ outputParameters: [],
31080
+ };
31081
+ for (const parameter of pipeline.parameters) {
31082
+ const { isInput, isOutput } = parameter;
31083
+ if (isInput) {
31084
+ pipelineInterface.inputParameters.push(deepClone(parameter));
31085
+ }
31086
+ if (isOutput) {
31087
+ pipelineInterface.outputParameters.push(deepClone(parameter));
31088
+ }
31089
+ }
31090
+ for (const key of ['inputParameters', 'outputParameters']) {
31091
+ pipelineInterface[key].sort(({ name: name1 }, { name: name2 }) => name1.localeCompare(name2));
30726
31092
  }
30727
31093
  return exportJson({
30728
31094
  name: `pipelineInterface`,
@@ -30960,157 +31326,125 @@ const HIGH_LEVEL_ABSTRACTIONS = [
30960
31326
  // Note: [💞] Ignore a discrepancy between file name and entity name
30961
31327
 
30962
31328
  /**
30963
- * Supported script languages
31329
+ * Applies postprocessing and exports the parsed pipeline JSON.
30964
31330
  *
30965
- * @private internal base for `ScriptLanguage`
31331
+ * @private internal utility of `parsePipeline`
30966
31332
  */
30967
- const SUPPORTED_SCRIPT_LANGUAGES = ['javascript', 'typescript', 'python'];
30968
- // <- TODO: [🏥] DRY
30969
-
31333
+ function finalizeParsedPipeline($pipelineJson) {
31334
+ applyImplicitParameterDirections($pipelineJson);
31335
+ removeUndefinedValuesFromPipeline($pipelineJson);
31336
+ applySyncHighLevelAbstractions($pipelineJson);
31337
+ ensurePipelineFormfactor($pipelineJson);
31338
+ return exportParsedPipelineJson($pipelineJson);
31339
+ }
30970
31340
  /**
30971
- * Removes Markdown (or HTML) comments
30972
- *
30973
- * @param {string} content - The string to remove comments from.
30974
- * @returns {string} The input string with all comments removed.
31341
+ * Applies default INPUT/OUTPUT flags when the author did not specify them explicitly.
30975
31342
  *
30976
- * @public exported from `@promptbook/markdown-utils`
31343
+ * @private internal utility of `finalizeParsedPipeline`
30977
31344
  */
30978
- function removeMarkdownComments(content) {
30979
- return spaceTrim$1(content.replace(/<!--(.*?)-->/gs, ''));
31345
+ function applyImplicitParameterDirections($pipelineJson) {
31346
+ markImplicitInputParameters($pipelineJson);
31347
+ markImplicitOutputParameters($pipelineJson);
30980
31348
  }
30981
-
30982
31349
  /**
30983
- * Utility to determine if a pipeline string is in flat format.
30984
- * A flat pipeline is a simple text without proper structure (headers, blocks, etc).
31350
+ * Marks non-result parameters as pipeline inputs when no input was declared.
30985
31351
  *
30986
- * @public exported from `@promptbook/editable`
31352
+ * @private internal utility of `finalizeParsedPipeline`
30987
31353
  */
30988
- function isFlatPipeline(pipelineString) {
30989
- pipelineString = removeMarkdownComments(pipelineString);
30990
- pipelineString = spaceTrim$1(pipelineString);
30991
- const isMarkdownBeginningWithHeadline = pipelineString.startsWith('# ');
30992
- //const isLastLineReturnStatement = pipelineString.split(/\r?\n/).pop()!.split('`').join('').startsWith('->');
30993
- const isBacktickBlockUsed = pipelineString.includes('```');
30994
- const isQuoteBlocksUsed = /^>\s+/m.test(pipelineString);
30995
- const isBlocksUsed = isBacktickBlockUsed || isQuoteBlocksUsed;
30996
- // TODO: [🧉] Also (double)check
30997
- // > const usedCommands
30998
- // > const isBlocksUsed
30999
- // > const returnStatementCount
31000
- const isFlat = !isMarkdownBeginningWithHeadline && !isBlocksUsed; /* && isLastLineReturnStatement */
31001
- return isFlat;
31354
+ function markImplicitInputParameters($pipelineJson) {
31355
+ if ($pipelineJson.parameters.some((parameter) => parameter.isInput)) {
31356
+ return;
31357
+ }
31358
+ for (const parameter of $pipelineJson.parameters) {
31359
+ const isThisParameterResulting = $pipelineJson.tasks.some((task) => task.resultingParameterName === parameter.name);
31360
+ if (!isThisParameterResulting) {
31361
+ parameter.isInput = true;
31362
+ // <- TODO: [💔] Why this is making typescript error in vscode but not in cli
31363
+ // > Type 'true' is not assignable to type 'false'.ts(2322)
31364
+ // > (property) isInput: false
31365
+ // > The parameter is input of the pipeline The parameter is NOT input of the pipeline
31366
+ }
31367
+ }
31002
31368
  }
31003
-
31004
31369
  /**
31005
- * Converts a pipeline structure to its string representation.
31006
- *
31007
- * Transforms a flat, simple pipeline into a properly formatted pipeline string
31008
- * with sections for title, prompt, and return statement.
31370
+ * Marks every non-input parameter as output when no output was declared.
31009
31371
  *
31010
- * @public exported from `@promptbook/editable`
31372
+ * @private internal utility of `finalizeParsedPipeline`
31011
31373
  */
31012
- function deflatePipeline(pipelineString) {
31013
- if (!isFlatPipeline(pipelineString)) {
31014
- return pipelineString;
31015
- }
31016
- pipelineString = spaceTrim$1(pipelineString);
31017
- const pipelineStringLines = pipelineString.split(/\r?\n/);
31018
- const potentialReturnStatement = pipelineStringLines.pop();
31019
- let returnStatement;
31020
- if (/(-|=)>\s*\{.*\}/.test(potentialReturnStatement)) {
31021
- // Note: Last line is return statement
31022
- returnStatement = potentialReturnStatement;
31023
- }
31024
- else {
31025
- // Note: Last line is not a return statement
31026
- returnStatement = `-> {${DEFAULT_BOOK_OUTPUT_PARAMETER_NAME}}`;
31027
- pipelineStringLines.push(potentialReturnStatement);
31028
- }
31029
- const prompt = spaceTrim$1(pipelineStringLines.join('\n'));
31030
- let quotedPrompt;
31031
- if (prompt.split(/\r?\n/).length <= 1) {
31032
- quotedPrompt = `> ${prompt}`;
31374
+ function markImplicitOutputParameters($pipelineJson) {
31375
+ if ($pipelineJson.parameters.some((parameter) => parameter.isOutput)) {
31376
+ return;
31033
31377
  }
31034
- else {
31035
- quotedPrompt = spaceTrim$1((block) => `
31036
- \`\`\`
31037
- ${block(prompt.split('`').join('\\`'))}
31038
- \`\`\`
31039
- `);
31378
+ for (const parameter of $pipelineJson.parameters) {
31379
+ if (!parameter.isInput) {
31380
+ parameter.isOutput = true;
31381
+ // <- TODO: [💔]
31382
+ }
31040
31383
  }
31041
- pipelineString = validatePipelineString(spaceTrim$1((block) => `
31042
- # ${DEFAULT_BOOK_TITLE}
31043
-
31044
- ## Prompt
31045
-
31046
- ${block(quotedPrompt)}
31047
-
31048
- ${returnStatement}
31049
- `));
31050
- // <- TODO: Maybe use book` notation
31051
- return padBook(pipelineString);
31052
31384
  }
31053
- // TODO: Unit test
31054
-
31055
31385
  /**
31056
- * Utility function to extract all list items from markdown
31057
- *
31058
- * Note: It works with both ul and ol
31059
- * Note: It omits list items in code blocks
31060
- * Note: It flattens nested lists
31061
- * Note: It can not work with html syntax and comments
31386
+ * Removes `undefined` properties from serialized tasks and parameters.
31062
31387
  *
31063
- * @param markdown any valid markdown
31064
- * @returns An array of strings, each representing an individual list item found in the markdown
31388
+ * @private internal utility of `finalizeParsedPipeline`
31389
+ */
31390
+ function removeUndefinedValuesFromPipeline($pipelineJson) {
31391
+ $pipelineJson.tasks.forEach(removeUndefinedProperties);
31392
+ $pipelineJson.parameters.forEach(removeUndefinedProperties);
31393
+ }
31394
+ /**
31395
+ * Deletes all own properties with `undefined` values from a mutable JSON entity.
31065
31396
  *
31066
- * @public exported from `@promptbook/markdown-utils`
31397
+ * @private internal utility of `finalizeParsedPipeline`
31067
31398
  */
31068
- function extractAllListItemsFromMarkdown(markdown) {
31069
- const lines = markdown.split(/\r?\n/);
31070
- const listItems = [];
31071
- let isInCodeBlock = false;
31072
- for (const line of lines) {
31073
- const trimmedLine = line.trim();
31074
- if (trimmedLine.startsWith('```')) {
31075
- isInCodeBlock = !isInCodeBlock;
31076
- }
31077
- if (!isInCodeBlock && (trimmedLine.startsWith('-') || trimmedLine.match(/^\d+\./))) {
31078
- const listItem = trimmedLine.replace(/^-|\d+\./, '').trim();
31079
- listItems.push(listItem);
31399
+ function removeUndefinedProperties(entity) {
31400
+ for (const [key, value] of Object.entries(entity)) {
31401
+ if (value === undefined) {
31402
+ delete entity[key];
31080
31403
  }
31081
31404
  }
31082
- return listItems;
31083
31405
  }
31084
-
31085
31406
  /**
31086
- * Extracts exactly ONE code block from markdown.
31087
- *
31088
- * - When there are multiple or no code blocks the function throws a `ParseError`
31089
- *
31090
- * Note: There are multiple similar functions:
31091
- * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
31092
- * - `extractJsonBlock` extracts exactly one valid JSON code block
31093
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
31094
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
31407
+ * Applies all sync-only high-level abstractions after parsing.
31095
31408
  *
31096
- * @param markdown any valid markdown
31097
- * @returns code block with language and content
31098
- * @throws {ParseError} if there is not exactly one code block in the markdown
31409
+ * @private internal utility of `finalizeParsedPipeline`
31410
+ */
31411
+ function applySyncHighLevelAbstractions($pipelineJson) {
31412
+ for (const highLevelAbstraction of HIGH_LEVEL_ABSTRACTIONS.filter(({ type }) => type === 'SYNC')) {
31413
+ highLevelAbstraction.$applyToPipelineJson($pipelineJson);
31414
+ }
31415
+ }
31416
+ /**
31417
+ * Ensures parsed pipelines always have the default `GENERIC` formfactor.
31099
31418
  *
31100
- * @public exported from `@promptbook/markdown-utils`
31419
+ * @private internal utility of `finalizeParsedPipeline`
31101
31420
  */
31102
- function extractOneBlockFromMarkdown(markdown) {
31103
- const codeBlocks = extractAllBlocksFromMarkdown(markdown);
31104
- if (codeBlocks.length !== 1) {
31105
- throw new ParseError(spaceTrim$1((block) => `
31106
- There should be exactly 1 code block in task section, found ${codeBlocks.length} code blocks
31107
-
31108
- ${block(codeBlocks.map((block, i) => `Block ${i + 1}:\n${block.content}`).join('\n\n\n'))}
31109
- `));
31421
+ function ensurePipelineFormfactor($pipelineJson) {
31422
+ // Note: [🔆] If formfactor is still not set, set it to 'GENERIC'
31423
+ if ($pipelineJson.formfactorName === undefined) {
31424
+ $pipelineJson.formfactorName = 'GENERIC';
31110
31425
  }
31111
- return codeBlocks[0];
31112
31426
  }
31113
- // TODO: [🍓][🌻] Decide of this is internal utility, external util OR validator/postprocessor
31427
+ /**
31428
+ * Finalizes ordering and exports the parsed pipeline JSON.
31429
+ *
31430
+ * @private internal utility of `finalizeParsedPipeline`
31431
+ */
31432
+ function exportParsedPipelineJson($pipelineJson) {
31433
+ return exportJson({
31434
+ name: 'pipelineJson',
31435
+ message: `Result of \`parsePipeline\``,
31436
+ order: ORDER_OF_PIPELINE_JSON,
31437
+ value: {
31438
+ formfactorName: 'GENERIC',
31439
+ // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
31440
+ ...$pipelineJson,
31441
+ },
31442
+ });
31443
+ }
31444
+ // TODO: Use spaceTrim more effectively
31445
+ // TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
31446
+ // TODO: [♈] Probably move expectations from tasks to parameters
31447
+ // TODO: [🍙] Make some standard order of json properties
31114
31448
 
31115
31449
  /**
31116
31450
  * Parses markdown section to title its level and content
@@ -31201,151 +31535,188 @@ function splitMarkdownIntoSections(markdown) {
31201
31535
  */
31202
31536
 
31203
31537
  /**
31204
- * Normalizes the markdown by flattening the structure
31538
+ * Splits the prepared markdown into the pipeline head and task sections.
31205
31539
  *
31206
- * - It always have h1 - if there is no h1 in the markdown, it will be added `DEFAULT_BOOK_TITLE`
31207
- * - All other headings are normalized to h2
31540
+ * @private internal utility of `parsePipeline`
31541
+ */
31542
+ function parsePreparedPipelineSections(pipelineString, $pipelineJson) {
31543
+ const [pipelineHead, ...pipelineSections] = splitMarkdownIntoSections(pipelineString).map(parseMarkdownSection); /* <- Note: [🥞] */
31544
+ assertPipelineSectionsStructure(pipelineHead, pipelineSections, $pipelineJson);
31545
+ return {
31546
+ pipelineHead,
31547
+ pipelineSections,
31548
+ };
31549
+ }
31550
+ /**
31551
+ * Ensures the flattened markdown has exactly one h1 head followed by only h2 sections.
31208
31552
  *
31209
- * @public exported from `@promptbook/markdown-utils`
31553
+ * @private internal utility of `parsePreparedPipelineSections`
31210
31554
  */
31211
- function flattenMarkdown(markdown) {
31212
- const sections = splitMarkdownIntoSections(markdown);
31213
- if (sections.length === 0) {
31214
- return `# ${DEFAULT_BOOK_TITLE}`;
31215
- }
31216
- let flattenedMarkdown = '';
31217
- const parsedSections = sections.map(parseMarkdownSection);
31218
- const firstSection = parsedSections.shift();
31219
- if (firstSection.level === 1) {
31220
- flattenedMarkdown += `# ${firstSection.title}` + `\n\n`;
31221
- flattenedMarkdown += firstSection.content + `\n\n`; // <- [🧠] Maybe 3 new lines?
31555
+ function assertPipelineSectionsStructure(pipelineHead, pipelineSections, $pipelineJson) {
31556
+ if (pipelineHead === undefined) {
31557
+ throw new UnexpectedError(spaceTrim$1((block) => `
31558
+ Pipeline head is not defined
31559
+
31560
+ ${block(getPipelineIdentification($pipelineJson))}
31561
+
31562
+ This should never happen, because the pipeline already flattened
31563
+ `));
31222
31564
  }
31223
- else {
31224
- parsedSections.unshift(firstSection);
31225
- flattenedMarkdown += `# ${DEFAULT_BOOK_TITLE}` + `\n\n`; // <- [🧠] Maybe 3 new lines?
31565
+ if (pipelineHead.level !== 1) {
31566
+ throw new UnexpectedError(spaceTrim$1((block) => `
31567
+ Pipeline head is not h1
31568
+
31569
+ ${block(getPipelineIdentification($pipelineJson))}
31570
+
31571
+ This should never happen, because the pipeline already flattened
31572
+ `));
31226
31573
  }
31227
- for (const { title, content } of parsedSections) {
31228
- flattenedMarkdown += `## ${title}` + `\n\n`;
31229
- flattenedMarkdown += content + `\n\n`; // <- [🧠] Maybe 3 new lines?
31574
+ if (!pipelineSections.every((pipelineSection) => pipelineSection.level === 2)) {
31575
+ throw new UnexpectedError(spaceTrim$1((block) => `
31576
+ Not every pipeline section is h2
31577
+
31578
+ ${block(getPipelineIdentification($pipelineJson))}
31579
+
31580
+ This should never happen, because the pipeline already flattened
31581
+ `));
31230
31582
  }
31231
- return spaceTrim$1(flattenedMarkdown);
31232
31583
  }
31233
- /**
31234
- * TODO: [🏛] This can be part of markdown builder
31235
- * Note: [🕞] In past (commit 42086e1603cbed506482997c00a8ee979af0a247) there was much more
31236
- * sophisticated implementation of this function through parsing markdown into JSON structure
31237
- * and flattening the actual structure
31238
- * NOW we are working just with markdown string and its good enough
31239
- */
31240
31584
 
31241
31585
  /**
31242
- * Normalizes inline parameter mentions wrapped in code spans before markdown flattening.
31243
- *
31244
- * @private internal utility of `parsePipeline`
31245
- */
31246
- const INLINE_CODE_PARAMETER_REGEXP = /`\{(?<parameterName>[a-z0-9_]+)\}`/gi;
31247
- /**
31248
- * Normalizes inline return statements wrapped in code spans before markdown flattening.
31586
+ * Removes Markdown (or HTML) comments
31249
31587
  *
31250
- * @private internal utility of `parsePipeline`
31251
- */
31252
- const INLINE_CODE_RETURN_PARAMETER_REGEXP = /`->\s+\{(?<parameterName>[a-z0-9_]+)\}`/gi;
31253
- /**
31254
- * Matches the trailing return statement of a task section.
31588
+ * @param {string} content - The string to remove comments from.
31589
+ * @returns {string} The input string with all comments removed.
31255
31590
  *
31256
- * @private internal utility of `parsePipeline`
31591
+ * @public exported from `@promptbook/markdown-utils`
31257
31592
  */
31258
- const RESULTING_PARAMETER_LINE_REGEXP = /^->\s*\{(?<resultingParamName>[a-z0-9_]+)\}/im;
31593
+ function removeMarkdownComments(content) {
31594
+ return spaceTrim$1(content.replace(/<!--(.*?)-->/gs, ''));
31595
+ }
31596
+
31259
31597
  /**
31260
- * Removes fenced code blocks when deriving human-readable section descriptions.
31598
+ * Utility to determine if a pipeline string is in flat format.
31599
+ * A flat pipeline is a simple text without proper structure (headers, blocks, etc).
31261
31600
  *
31262
- * @private internal utility of `parsePipeline`
31601
+ * @public exported from `@promptbook/editable`
31263
31602
  */
31264
- const DESCRIPTION_CODE_BLOCK_REGEXP = /^```.*^```/gms;
31603
+ function isFlatPipeline(pipelineString) {
31604
+ pipelineString = removeMarkdownComments(pipelineString);
31605
+ pipelineString = spaceTrim$1(pipelineString);
31606
+ const isMarkdownBeginningWithHeadline = pipelineString.startsWith('# ');
31607
+ //const isLastLineReturnStatement = pipelineString.split(/\r?\n/).pop()!.split('`').join('').startsWith('->');
31608
+ const isBacktickBlockUsed = pipelineString.includes('```');
31609
+ const isQuoteBlocksUsed = /^>\s+/m.test(pipelineString);
31610
+ const isBlocksUsed = isBacktickBlockUsed || isQuoteBlocksUsed;
31611
+ // TODO: [🧉] Also (double)check
31612
+ // > const usedCommands
31613
+ // > const isBlocksUsed
31614
+ // > const returnStatementCount
31615
+ const isFlat = !isMarkdownBeginningWithHeadline && !isBlocksUsed; /* && isLastLineReturnStatement */
31616
+ return isFlat;
31617
+ }
31618
+
31265
31619
  /**
31266
- * Removes blockquote lines when deriving human-readable section descriptions.
31620
+ * Converts a pipeline structure to its string representation.
31267
31621
  *
31268
- * @private internal utility of `parsePipeline`
31269
- */
31270
- const DESCRIPTION_BLOCKQUOTE_REGEXP = /^>.*$/gm;
31271
- /**
31272
- * Removes list items and return statements when deriving human-readable section descriptions.
31622
+ * Transforms a flat, simple pipeline into a properly formatted pipeline string
31623
+ * with sections for title, prompt, and return statement.
31273
31624
  *
31274
- * @private internal utility of `parsePipeline`
31625
+ * @public exported from `@promptbook/editable`
31275
31626
  */
31276
- const DESCRIPTION_LIST_ITEM_REGEXP = /^(?:(?:-)|(?:\d\))|(?:`?->))\s+.*$/gm;
31627
+ function deflatePipeline(pipelineString) {
31628
+ if (!isFlatPipeline(pipelineString)) {
31629
+ return pipelineString;
31630
+ }
31631
+ pipelineString = spaceTrim$1(pipelineString);
31632
+ const pipelineStringLines = pipelineString.split(/\r?\n/);
31633
+ const potentialReturnStatement = pipelineStringLines.pop();
31634
+ let returnStatement;
31635
+ if (/(-|=)>\s*\{.*\}/.test(potentialReturnStatement)) {
31636
+ // Note: Last line is return statement
31637
+ returnStatement = potentialReturnStatement;
31638
+ }
31639
+ else {
31640
+ // Note: Last line is not a return statement
31641
+ returnStatement = `-> {${DEFAULT_BOOK_OUTPUT_PARAMETER_NAME}}`;
31642
+ pipelineStringLines.push(potentialReturnStatement);
31643
+ }
31644
+ const prompt = spaceTrim$1(pipelineStringLines.join('\n'));
31645
+ let quotedPrompt;
31646
+ if (prompt.split(/\r?\n/).length <= 1) {
31647
+ quotedPrompt = `> ${prompt}`;
31648
+ }
31649
+ else {
31650
+ quotedPrompt = spaceTrim$1((block) => `
31651
+ \`\`\`
31652
+ ${block(prompt.split('`').join('\\`'))}
31653
+ \`\`\`
31654
+ `);
31655
+ }
31656
+ pipelineString = validatePipelineString(spaceTrim$1((block) => `
31657
+ # ${DEFAULT_BOOK_TITLE}
31658
+
31659
+ ## Prompt
31660
+
31661
+ ${block(quotedPrompt)}
31662
+
31663
+ ${returnStatement}
31664
+ `));
31665
+ // <- TODO: Maybe use book` notation
31666
+ return padBook(pipelineString);
31667
+ }
31668
+ // TODO: Unit test
31669
+
31277
31670
  /**
31278
- * Compile pipeline from string (markdown) format to JSON format synchronously
31279
- *
31280
- * Note: There are 3 similar functions:
31281
- * - `compilePipeline` **(preferred)** - which properly compiles the promptbook and uses embedding for external knowledge
31282
- * - `parsePipeline` - use only if you need to compile promptbook synchronously and it contains NO external knowledge
31283
- * - `preparePipeline` - just one step in the compilation process
31284
- *
31285
- * Note: This function does not validate logic of the pipeline only the parsing
31286
- * Note: This function acts as compilation process
31671
+ * Normalizes the markdown by flattening the structure
31287
31672
  *
31288
- * @param pipelineString {Promptbook} in string markdown format (.book.md)
31289
- * @returns {Promptbook} compiled in JSON format (.bookc)
31290
- * @throws {ParseError} if the promptbook string is not valid
31673
+ * - It always have h1 - if there is no h1 in the markdown, it will be added `DEFAULT_BOOK_TITLE`
31674
+ * - All other headings are normalized to h2
31291
31675
  *
31292
- * @public exported from `@promptbook/core`
31676
+ * @public exported from `@promptbook/markdown-utils`
31293
31677
  */
31294
- function parsePipeline(pipelineString) {
31295
- const $pipelineJson = createInitialPipelineJson(pipelineString);
31296
- const preparedPipelineString = preparePipelineString(pipelineString, $pipelineJson);
31297
- const { pipelineHead, pipelineSections } = parsePreparedPipelineSections(preparedPipelineString, $pipelineJson);
31298
- const getUniqueSectionName = createUniqueSectionNameResolver(pipelineSections);
31299
- applyPipelineHead(pipelineHead, $pipelineJson);
31300
- for (const pipelineSection of pipelineSections) {
31301
- processPipelineSection(pipelineSection, $pipelineJson, getUniqueSectionName);
31678
+ function flattenMarkdown(markdown) {
31679
+ const sections = splitMarkdownIntoSections(markdown);
31680
+ if (sections.length === 0) {
31681
+ return `# ${DEFAULT_BOOK_TITLE}`;
31302
31682
  }
31303
- applyImplicitParameterDirections($pipelineJson);
31304
- removeUndefinedValuesFromPipeline($pipelineJson);
31305
- applySyncHighLevelAbstractions($pipelineJson);
31306
- ensurePipelineFormfactor($pipelineJson);
31307
- return exportParsedPipelineJson($pipelineJson);
31683
+ let flattenedMarkdown = '';
31684
+ const parsedSections = sections.map(parseMarkdownSection);
31685
+ const firstSection = parsedSections.shift();
31686
+ if (firstSection.level === 1) {
31687
+ flattenedMarkdown += `# ${firstSection.title}` + `\n\n`;
31688
+ flattenedMarkdown += firstSection.content + `\n\n`; // <- [🧠] Maybe 3 new lines?
31689
+ }
31690
+ else {
31691
+ parsedSections.unshift(firstSection);
31692
+ flattenedMarkdown += `# ${DEFAULT_BOOK_TITLE}` + `\n\n`; // <- [🧠] Maybe 3 new lines?
31693
+ }
31694
+ for (const { title, content } of parsedSections) {
31695
+ flattenedMarkdown += `## ${title}` + `\n\n`;
31696
+ flattenedMarkdown += content + `\n\n`; // <- [🧠] Maybe 3 new lines?
31697
+ }
31698
+ return spaceTrim$1(flattenedMarkdown);
31308
31699
  }
31309
31700
  /**
31310
- * Creates the mutable pipeline JSON structure used throughout parsing.
31701
+ * TODO: [🏛] This can be part of markdown builder
31702
+ * Note: [🕞] In past (commit 42086e1603cbed506482997c00a8ee979af0a247) there was much more
31703
+ * sophisticated implementation of this function through parsing markdown into JSON structure
31704
+ * and flattening the actual structure
31705
+ * NOW we are working just with markdown string and its good enough
31706
+ */
31707
+
31708
+ /**
31709
+ * Normalizes inline parameter mentions wrapped in code spans before markdown flattening.
31311
31710
  *
31312
- * @private internal utility of `parsePipeline`
31711
+ * @private internal utility of `preparePipelineString`
31313
31712
  */
31314
- function createInitialPipelineJson(pipelineString) {
31315
- return {
31316
- title: DEFAULT_BOOK_TITLE,
31317
- parameters: [],
31318
- tasks: [],
31319
- knowledgeSources: [],
31320
- knowledgePieces: [],
31321
- personas: [],
31322
- preparations: [],
31323
- sources: [
31324
- {
31325
- type: 'BOOK',
31326
- path: null,
31327
- // <- TODO: !!6 Pass here path of the file
31328
- content: pipelineString,
31329
- },
31330
- ],
31331
- };
31332
- }
31713
+ const INLINE_CODE_PARAMETER_REGEXP = /`\{(?<parameterName>[a-z0-9_]+)\}`/gi;
31333
31714
  /**
31334
- * Builds a short file/url identification block for parse errors.
31715
+ * Normalizes inline return statements wrapped in code spans before markdown flattening.
31335
31716
  *
31336
- * @private internal utility of `parsePipeline`
31717
+ * @private internal utility of `preparePipelineString`
31337
31718
  */
31338
- function getPipelineIdentification($pipelineJson) {
31339
- // Note: This is a 😐 implementation of [🚞]
31340
- const pipelineIdentificationParts = [];
31341
- if ($pipelineJson.sourceFile !== undefined) {
31342
- pipelineIdentificationParts.push(`File: ${$pipelineJson.sourceFile}`);
31343
- }
31344
- if ($pipelineJson.pipelineUrl !== undefined) {
31345
- pipelineIdentificationParts.push(`Url: ${$pipelineJson.pipelineUrl}`);
31346
- }
31347
- return pipelineIdentificationParts.join('\n');
31348
- }
31719
+ const INLINE_CODE_RETURN_PARAMETER_REGEXP = /`->\s+\{(?<parameterName>[a-z0-9_]+)\}`/gi;
31349
31720
  /**
31350
31721
  * Removes shebang/comments and normalizes markdown into a parseable pipeline form.
31351
31722
  *
@@ -31362,222 +31733,78 @@ function preparePipelineString(pipelineString, $pipelineJson) {
31362
31733
  pipelineString = pipelineString.replaceAll(INLINE_CODE_RETURN_PARAMETER_REGEXP, '-> {$<parameterName>}');
31363
31734
  return pipelineString;
31364
31735
  }
31365
- /**
31366
- * Validates and removes the optional `#!` shebang line for `.book` files.
31367
- *
31368
- * @private internal utility of `parsePipeline`
31369
- */
31370
- function removePipelineShebang(pipelineString, $pipelineJson) {
31371
- if (!pipelineString.startsWith('#!')) {
31372
- return pipelineString;
31373
- }
31374
- const [shebangLine, ...restLines] = pipelineString.split(/\r?\n/);
31375
- const isBookShebang = (shebangLine || '').includes('ptbk');
31376
- if (!isBookShebang) {
31377
- throw new ParseError(spaceTrim$1((block) => `
31378
- It seems that you try to parse a book file which has non-standard shebang line for book files:
31379
- Shebang line must contain 'ptbk'
31380
-
31381
- You have:
31382
- ${block(shebangLine || '(empty line)')}
31383
-
31384
- It should look like this:
31385
- #!/usr/bin/env ptbk
31386
-
31387
- ${block(getPipelineIdentification($pipelineJson))}
31388
- `));
31389
- }
31390
- return validatePipelineString(restLines.join('\n'));
31391
- }
31392
- /**
31393
- * Splits the prepared markdown into the pipeline head and task sections.
31394
- *
31395
- * @private internal utility of `parsePipeline`
31396
- */
31397
- function parsePreparedPipelineSections(pipelineString, $pipelineJson) {
31398
- const [pipelineHead, ...pipelineSections] = splitMarkdownIntoSections(pipelineString).map(parseMarkdownSection); /* <- Note: [🥞] */
31399
- assertPipelineSectionsStructure(pipelineHead, pipelineSections, $pipelineJson);
31400
- return {
31401
- pipelineHead,
31402
- pipelineSections,
31403
- };
31404
- }
31405
- /**
31406
- * Ensures the flattened markdown has exactly one h1 head followed by only h2 sections.
31407
- *
31408
- * @private internal utility of `parsePipeline`
31409
- */
31410
- function assertPipelineSectionsStructure(pipelineHead, pipelineSections, $pipelineJson) {
31411
- if (pipelineHead === undefined) {
31412
- throw new UnexpectedError(spaceTrim$1((block) => `
31413
- Pipeline head is not defined
31414
-
31415
- ${block(getPipelineIdentification($pipelineJson))}
31416
-
31417
- This should never happen, because the pipeline already flattened
31418
- `));
31419
- }
31420
- if (pipelineHead.level !== 1) {
31421
- throw new UnexpectedError(spaceTrim$1((block) => `
31422
- Pipeline head is not h1
31423
-
31424
- ${block(getPipelineIdentification($pipelineJson))}
31425
-
31426
- This should never happen, because the pipeline already flattened
31427
- `));
31428
- }
31429
- if (!pipelineSections.every((pipelineSection) => pipelineSection.level === 2)) {
31430
- throw new UnexpectedError(spaceTrim$1((block) => `
31431
- Not every pipeline section is h2
31432
-
31433
- ${block(getPipelineIdentification($pipelineJson))}
31434
-
31435
- This should never happen, because the pipeline already flattened
31436
- `));
31437
- }
31438
- }
31439
- /**
31440
- * Applies the pipeline head title, description, and head-level commands.
31441
- *
31442
- * @private internal utility of `parsePipeline`
31443
- */
31444
- function applyPipelineHead(pipelineHead, $pipelineJson) {
31445
- $pipelineJson.title = pipelineHead.title;
31446
- $pipelineJson.description = extractPipelineDescription(pipelineHead.content);
31447
- for (const listItem of extractAllListItemsFromMarkdown(pipelineHead.content)) {
31448
- applyPipelineHeadCommand(listItem, $pipelineJson);
31449
- }
31450
- }
31451
- /**
31452
- * Extracts the plain-text description from a head or task section body.
31453
- *
31454
- * @private internal utility of `parsePipeline`
31455
- */
31456
- function extractPipelineDescription(sectionContent) {
31457
- let description = sectionContent;
31458
- description = description.split(DESCRIPTION_CODE_BLOCK_REGEXP).join('');
31459
- description = description.split(DESCRIPTION_BLOCKQUOTE_REGEXP).join('');
31460
- description = description.split(DESCRIPTION_LIST_ITEM_REGEXP).join('');
31461
- description = spaceTrim$1(description);
31462
- if (description === '') {
31463
- return undefined;
31464
- }
31465
- return description;
31466
- }
31467
- /**
31468
- * Parses and applies one command declared in the pipeline head.
31469
- *
31470
- * @private internal utility of `parsePipeline`
31471
- */
31472
- function applyPipelineHeadCommand(listItem, $pipelineJson) {
31473
- const command = parseCommand(listItem, 'PIPELINE_HEAD');
31474
- const commandParser = getParserForCommand(command);
31475
- if (commandParser.isUsedInPipelineHead !== true /* <- Note: [🦦][4] */) {
31476
- throw new ParseError(spaceTrim$1((block) => `
31477
- Command \`${command.type}\` is not allowed in the head of the pipeline ONLY at the pipeline task
31478
-
31479
- ${block(getPipelineIdentification($pipelineJson))}
31480
- `)); // <- TODO: [🚞]
31481
- }
31482
- try {
31483
- commandParser.$applyToPipelineJson(command, $pipelineJson);
31484
- // <- Note: [🦦] Its strange that this assertion must be here, [🦦][4] should do this assertion implicitly
31736
+ /**
31737
+ * Validates and removes the optional `#!` shebang line for `.book` files.
31738
+ *
31739
+ * @private internal utility of `preparePipelineString`
31740
+ */
31741
+ function removePipelineShebang(pipelineString, $pipelineJson) {
31742
+ if (!pipelineString.startsWith('#!')) {
31743
+ return pipelineString;
31485
31744
  }
31486
- catch (error) {
31487
- if (!(error instanceof ParseError)) {
31488
- throw error;
31489
- }
31745
+ const [shebangLine, ...restLines] = pipelineString.split(/\r?\n/);
31746
+ const isBookShebang = (shebangLine || '').includes('ptbk');
31747
+ if (!isBookShebang) {
31490
31748
  throw new ParseError(spaceTrim$1((block) => `
31491
- Command ${command.type} failed to apply to the pipeline
31492
-
31493
- The error:
31494
- ${block(error.message)}
31749
+ It seems that you try to parse a book file which has non-standard shebang line for book files:
31750
+ Shebang line must contain 'ptbk'
31495
31751
 
31496
- Raw command:
31497
- - ${listItem}
31752
+ You have:
31753
+ ${block(shebangLine || '(empty line)')}
31498
31754
 
31499
- Usage of ${command.type}:
31500
- ${block(commandParser.examples.map((example) => `- ${example}`).join('\n'))}
31755
+ It should look like this:
31756
+ #!/usr/bin/env ptbk
31501
31757
 
31502
31758
  ${block(getPipelineIdentification($pipelineJson))}
31503
- `)); // <- TODO: [🚞]
31504
- }
31505
- if (command.type === 'PARAMETER') {
31506
- defineParameter($pipelineJson, command);
31507
- // <- Note: [🍣]
31759
+ `));
31508
31760
  }
31761
+ return validatePipelineString(restLines.join('\n'));
31509
31762
  }
31763
+
31510
31764
  /**
31511
- * Merges one parameter declaration into the mutable pipeline parameter list.
31765
+ * Supported script languages
31512
31766
  *
31513
- * @private internal utility of `parsePipeline`
31767
+ * @private internal base for `ScriptLanguage`
31514
31768
  */
31515
- function defineParameter($pipelineJson, parameterCommand) {
31516
- const { parameterName, parameterDescription, isInput, isOutput } = parameterCommand;
31517
- if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
31518
- throw new ParseError(spaceTrim$1((block) => `
31519
- Parameter name {${parameterName}} is reserved and cannot be used as resulting parameter name
31769
+ const SUPPORTED_SCRIPT_LANGUAGES = ['javascript', 'typescript', 'python'];
31770
+ // <- TODO: [🏥] DRY
31520
31771
 
31521
- ${block(getPipelineIdentification($pipelineJson))}
31522
- `) /* <- TODO: [🚞] */);
31523
- }
31524
- const existingParameter = $pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
31525
- if (existingParameter &&
31526
- existingParameter.description &&
31527
- existingParameter.description !== parameterDescription &&
31528
- parameterDescription) {
31772
+ /**
31773
+ * Extracts exactly ONE code block from markdown.
31774
+ *
31775
+ * - When there are multiple or no code blocks the function throws a `ParseError`
31776
+ *
31777
+ * Note: There are multiple similar functions:
31778
+ * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
31779
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
31780
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
31781
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
31782
+ *
31783
+ * @param markdown any valid markdown
31784
+ * @returns code block with language and content
31785
+ * @throws {ParseError} if there is not exactly one code block in the markdown
31786
+ *
31787
+ * @public exported from `@promptbook/markdown-utils`
31788
+ */
31789
+ function extractOneBlockFromMarkdown(markdown) {
31790
+ const codeBlocks = extractAllBlocksFromMarkdown(markdown);
31791
+ if (codeBlocks.length !== 1) {
31529
31792
  throw new ParseError(spaceTrim$1((block) => `
31530
- Parameter \`{${parameterName}}\` is defined multiple times with different description:
31531
-
31532
- ${block(getPipelineIdentification($pipelineJson))}
31533
-
31534
- First definition:
31535
- ${block(existingParameter.description || '[undefined]')}
31793
+ There should be exactly 1 code block in task section, found ${codeBlocks.length} code blocks
31536
31794
 
31537
- Second definition:
31538
- ${block(parameterDescription || '[undefined]')}
31795
+ ${block(codeBlocks.map((block, i) => `Block ${i + 1}:\n${block.content}`).join('\n\n\n'))}
31539
31796
  `));
31540
31797
  }
31541
- if (existingParameter) {
31542
- if (parameterDescription) {
31543
- existingParameter.description = parameterDescription;
31544
- }
31545
- existingParameter.isInput = existingParameter.isInput || isInput;
31546
- existingParameter.isOutput = existingParameter.isOutput || isOutput;
31547
- return;
31548
- }
31549
- $pipelineJson.parameters.push({
31550
- name: parameterName,
31551
- description: parameterDescription || undefined,
31552
- isInput,
31553
- isOutput,
31554
- });
31798
+ return codeBlocks[0];
31555
31799
  }
31800
+ // TODO: [🍓][🌻] Decide of this is internal utility, external util OR validator/postprocessor
31801
+
31556
31802
  /**
31557
- * Creates stable unique task names for duplicate section titles.
31803
+ * Matches the trailing return statement of a task section.
31558
31804
  *
31559
- * @private internal utility of `parsePipeline`
31805
+ * @private internal utility of `processPipelineSection`
31560
31806
  */
31561
- function createUniqueSectionNameResolver(pipelineSections) {
31562
- const sectionCounts = {};
31563
- for (const pipelineSection of pipelineSections) {
31564
- const sectionName = titleToName(pipelineSection.title);
31565
- if (sectionCounts[sectionName] === undefined) {
31566
- sectionCounts[sectionName] = { count: 0, currentIndex: 0 };
31567
- }
31568
- sectionCounts[sectionName].count++;
31569
- }
31570
- return (title) => {
31571
- const sectionName = titleToName(title);
31572
- const sectionCount = sectionCounts[sectionName];
31573
- if (sectionCount.count === 1) {
31574
- return sectionName;
31575
- }
31576
- const nameWithSuffix = `${sectionName}-${sectionCount.currentIndex}`;
31577
- sectionCount.currentIndex++;
31578
- return nameWithSuffix;
31579
- };
31580
- }
31807
+ const RESULTING_PARAMETER_LINE_REGEXP = /^->\s*\{(?<resultingParamName>[a-z0-9_]+)\}/im;
31581
31808
  /**
31582
31809
  * Parses, applies, and persists one h2 task section.
31583
31810
  *
@@ -31597,7 +31824,7 @@ function processPipelineSection(pipelineSection, $pipelineJson, getUniqueSection
31597
31824
  /**
31598
31825
  * Creates the mutable task JSON shell from one markdown section.
31599
31826
  *
31600
- * @private internal utility of `parsePipeline`
31827
+ * @private internal utility of `processPipelineSection`
31601
31828
  */
31602
31829
  function createTaskJsonFromSection(pipelineSection, getUniqueSectionName) {
31603
31830
  const { language, content } = extractOneBlockFromMarkdown(pipelineSection.content);
@@ -31625,7 +31852,7 @@ function createTaskJsonFromSection(pipelineSection, getUniqueSectionName) {
31625
31852
  /**
31626
31853
  * Extracts the optional trailing `-> {parameter}` statement from a section body.
31627
31854
  *
31628
- * @private internal utility of `parsePipeline`
31855
+ * @private internal utility of `processPipelineSection`
31629
31856
  */
31630
31857
  function extractResultingParameterName(sectionContent) {
31631
31858
  var _a;
@@ -31636,7 +31863,7 @@ function extractResultingParameterName(sectionContent) {
31636
31863
  /**
31637
31864
  * Parses all list-item commands declared inside one task section.
31638
31865
  *
31639
- * @private internal utility of `parsePipeline`
31866
+ * @private internal utility of `processPipelineSection`
31640
31867
  */
31641
31868
  function parsePipelineTaskCommands(sectionContent) {
31642
31869
  return extractAllListItemsFromMarkdown(sectionContent).map((listItem) => ({
@@ -31647,7 +31874,7 @@ function parsePipelineTaskCommands(sectionContent) {
31647
31874
  /**
31648
31875
  * Applies the implicit default `PROMPT_TASK` section type when no SECTION command is present.
31649
31876
  *
31650
- * @private internal utility of `parsePipeline`
31877
+ * @private internal utility of `processPipelineSection`
31651
31878
  */
31652
31879
  function applyDefaultTaskSectionType($taskJson, commands, $pipelineJson) {
31653
31880
  const isSectionCommandPresent = commands.some(({ command }) => command.type === 'SECTION');
@@ -31659,7 +31886,7 @@ function applyDefaultTaskSectionType($taskJson, commands, $pipelineJson) {
31659
31886
  /**
31660
31887
  * Parses and applies one command declared inside a task section.
31661
31888
  *
31662
- * @private internal utility of `parsePipeline`
31889
+ * @private internal utility of `processPipelineSection`
31663
31890
  */
31664
31891
  function applyPipelineTaskCommand(commandItem, $taskJson, $pipelineJson) {
31665
31892
  const { listItem, command } = commandItem;
@@ -31710,7 +31937,7 @@ function applyPipelineTaskCommand(commandItem, $taskJson, $pipelineJson) {
31710
31937
  /**
31711
31938
  * Validates and stores the language for SCRIPT tasks.
31712
31939
  *
31713
- * @private internal utility of `parsePipeline`
31940
+ * @private internal utility of `processPipelineSection`
31714
31941
  */
31715
31942
  function applyScriptTaskLanguage($taskJson, language, $pipelineJson) {
31716
31943
  const isScriptTask = $taskJson.taskType === 'SCRIPT_TASK';
@@ -31727,7 +31954,6 @@ function applyScriptTaskLanguage($taskJson, language, $pipelineJson) {
31727
31954
  if (!SUPPORTED_SCRIPT_LANGUAGES.includes(language)) {
31728
31955
  throw new ParseError(spaceTrim$1((block) => `
31729
31956
  Script language ${language} is not supported.
31730
-
31731
31957
  Supported languages are:
31732
31958
  ${block(SUPPORTED_SCRIPT_LANGUAGES.join(', '))}
31733
31959
 
@@ -31738,7 +31964,7 @@ function applyScriptTaskLanguage($taskJson, language, $pipelineJson) {
31738
31964
  /**
31739
31965
  * Extracts task dependencies and ensures referenced parameters exist.
31740
31966
  *
31741
- * @private internal utility of `parsePipeline`
31967
+ * @private internal utility of `processPipelineSection`
31742
31968
  */
31743
31969
  function registerTaskDependentParameters($taskJson, $pipelineJson) {
31744
31970
  $taskJson.dependentParameterNames = Array.from(extractParameterNamesFromTask($taskJson));
@@ -31756,7 +31982,7 @@ function registerTaskDependentParameters($taskJson, $pipelineJson) {
31756
31982
  /**
31757
31983
  * Removes transient parsing flags and persists real tasks into the pipeline JSON.
31758
31984
  *
31759
- * @private internal utility of `parsePipeline`
31985
+ * @private internal utility of `processPipelineSection`
31760
31986
  */
31761
31987
  function persistTaskIfNeeded($taskJson, $pipelineJson) {
31762
31988
  /*
@@ -31785,119 +32011,38 @@ function persistTaskIfNeeded($taskJson, $pipelineJson) {
31785
32011
  // TODO: [🍙] Maybe do reorder of `$taskJson` here
31786
32012
  $pipelineJson.tasks.push($taskJson);
31787
32013
  }
32014
+
31788
32015
  /**
31789
- * Applies default INPUT/OUTPUT flags when the author did not specify them explicitly.
31790
- *
31791
- * @private internal utility of `parsePipeline`
31792
- */
31793
- function applyImplicitParameterDirections($pipelineJson) {
31794
- markImplicitInputParameters($pipelineJson);
31795
- markImplicitOutputParameters($pipelineJson);
31796
- }
31797
- /**
31798
- * Marks non-result parameters as pipeline inputs when no input was declared.
31799
- *
31800
- * @private internal utility of `parsePipeline`
31801
- */
31802
- function markImplicitInputParameters($pipelineJson) {
31803
- if ($pipelineJson.parameters.some((parameter) => parameter.isInput)) {
31804
- return;
31805
- }
31806
- for (const parameter of $pipelineJson.parameters) {
31807
- const isThisParameterResulting = $pipelineJson.tasks.some((task) => task.resultingParameterName === parameter.name);
31808
- if (!isThisParameterResulting) {
31809
- parameter.isInput = true;
31810
- // <- TODO: [💔] Why this is making typescript error in vscode but not in cli
31811
- // > Type 'true' is not assignable to type 'false'.ts(2322)
31812
- // > (property) isInput: false
31813
- // > The parameter is input of the pipeline The parameter is NOT input of the pipeline
31814
- }
31815
- }
31816
- }
31817
- /**
31818
- * Marks every non-input parameter as output when no output was declared.
31819
- *
31820
- * @private internal utility of `parsePipeline`
31821
- */
31822
- function markImplicitOutputParameters($pipelineJson) {
31823
- if ($pipelineJson.parameters.some((parameter) => parameter.isOutput)) {
31824
- return;
31825
- }
31826
- for (const parameter of $pipelineJson.parameters) {
31827
- if (!parameter.isInput) {
31828
- parameter.isOutput = true;
31829
- // <- TODO: [💔]
31830
- }
31831
- }
31832
- }
31833
- /**
31834
- * Removes `undefined` properties from serialized tasks and parameters.
32016
+ * Compile pipeline from string (markdown) format to JSON format synchronously
31835
32017
  *
31836
- * @private internal utility of `parsePipeline`
31837
- */
31838
- function removeUndefinedValuesFromPipeline($pipelineJson) {
31839
- $pipelineJson.tasks.forEach(removeUndefinedProperties);
31840
- $pipelineJson.parameters.forEach(removeUndefinedProperties);
31841
- }
31842
- /**
31843
- * Deletes all own properties with `undefined` values from a mutable JSON entity.
32018
+ * Note: There are 3 similar functions:
32019
+ * - `compilePipeline` **(preferred)** - which properly compiles the promptbook and uses embedding for external knowledge
32020
+ * - `parsePipeline` - use only if you need to compile promptbook synchronously and it contains NO external knowledge
32021
+ * - `preparePipeline` - just one step in the compilation process
31844
32022
  *
31845
- * @private internal utility of `parsePipeline`
31846
- */
31847
- function removeUndefinedProperties(entity) {
31848
- for (const [key, value] of Object.entries(entity)) {
31849
- if (value === undefined) {
31850
- delete entity[key];
31851
- }
31852
- }
31853
- }
31854
- /**
31855
- * Applies all sync-only high-level abstractions after parsing.
32023
+ * Note: This function does not validate logic of the pipeline only the parsing
32024
+ * Note: This function acts as compilation process
31856
32025
  *
31857
- * @private internal utility of `parsePipeline`
31858
- */
31859
- function applySyncHighLevelAbstractions($pipelineJson) {
31860
- for (const highLevelAbstraction of HIGH_LEVEL_ABSTRACTIONS.filter(({ type }) => type === 'SYNC')) {
31861
- highLevelAbstraction.$applyToPipelineJson($pipelineJson);
31862
- }
31863
- }
31864
- /**
31865
- * Ensures parsed pipelines always have the default `GENERIC` formfactor.
32026
+ * @param pipelineString {Promptbook} in string markdown format (.book.md)
32027
+ * @returns {Promptbook} compiled in JSON format (.bookc)
32028
+ * @throws {ParseError} if the promptbook string is not valid
31866
32029
  *
31867
- * @private internal utility of `parsePipeline`
32030
+ * @public exported from `@promptbook/core`
31868
32031
  */
31869
- function ensurePipelineFormfactor($pipelineJson) {
31870
- // Note: [🔆] If formfactor is still not set, set it to 'GENERIC'
31871
- if ($pipelineJson.formfactorName === undefined) {
31872
- $pipelineJson.formfactorName = 'GENERIC';
32032
+ function parsePipeline(pipelineString) {
32033
+ const $pipelineJson = createInitialPipelineJson(pipelineString);
32034
+ const preparedPipelineString = preparePipelineString(pipelineString, $pipelineJson);
32035
+ const { pipelineHead, pipelineSections } = parsePreparedPipelineSections(preparedPipelineString, $pipelineJson);
32036
+ const getUniqueSectionName = createUniqueSectionNameResolver(pipelineSections);
32037
+ applyPipelineHead(pipelineHead, $pipelineJson);
32038
+ for (const pipelineSection of pipelineSections) {
32039
+ processPipelineSection(pipelineSection, $pipelineJson, getUniqueSectionName);
31873
32040
  }
31874
- }
31875
- /**
31876
- * Finalizes ordering and exports the parsed pipeline JSON.
31877
- *
31878
- * @private internal utility of `parsePipeline`
31879
- */
31880
- function exportParsedPipelineJson($pipelineJson) {
31881
- return exportJson({
31882
- name: 'pipelineJson',
31883
- message: `Result of \`parsePipeline\``,
31884
- order: ORDER_OF_PIPELINE_JSON,
31885
- value: {
31886
- formfactorName: 'GENERIC',
31887
- // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
31888
- ...$pipelineJson,
31889
- },
31890
- });
32041
+ return finalizeParsedPipeline($pipelineJson);
31891
32042
  }
31892
32043
  // TODO: [🧠] Maybe more things here can be refactored as high-level abstractions
31893
32044
  // TODO: [main] !!4 Warn if used only sync version
31894
32045
  // TODO: [🚞] Report here line/column of error
31895
- // TODO: Use spaceTrim more effectively
31896
- // TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
31897
- // TODO: [🥞] Not optimal parsing because `splitMarkdownIntoSections` is executed twice with same string, once through `flattenMarkdown` and second directly here
31898
- // TODO: [♈] Probably move expectations from tasks to parameters
31899
- // TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
31900
- // TODO: [🍙] Make some standard order of json properties
31901
32046
 
31902
32047
  /**
31903
32048
  * Compile pipeline from string (markdown) format to JSON format