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