@promptbook/node 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 +1403 -1272
  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 +2 -2
  24. package/umd/index.umd.js +1403 -1272
  25. package/umd/index.umd.js.map +1 -1
  26. package/umd/src/book-3.0/Book.d.ts +6 -0
  27. package/umd/src/book-components/Chat/utils/getToolCallChipletInfo.test.d.ts +1 -0
  28. package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +12 -2
  29. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  30. package/umd/src/cli/cli-commands/run/prepareRunCommandResources.d.ts +20 -0
  31. package/umd/src/cli/cli-commands/run/resolveRunInputParameters.d.ts +12 -0
  32. package/umd/src/cli/cli-commands/run/runCommandAction.d.ts +21 -0
  33. package/umd/src/cli/cli-commands/run/runPipelineExecution.d.ts +14 -0
  34. package/umd/src/cli/cli-commands/run.d.ts +1 -1
  35. package/umd/src/conversion/parsePipeline/applyPipelineHead.d.ts +8 -0
  36. package/umd/src/conversion/parsePipeline/createInitialPipelineJson.d.ts +8 -0
  37. package/umd/src/conversion/parsePipeline/createUniqueSectionNameResolver.d.ts +14 -0
  38. package/umd/src/conversion/parsePipeline/defineParameter.d.ts +8 -0
  39. package/umd/src/conversion/parsePipeline/extractPipelineDescription.d.ts +6 -0
  40. package/umd/src/conversion/parsePipeline/finalizeParsedPipeline.d.ts +8 -0
  41. package/umd/src/conversion/parsePipeline/getPipelineIdentification.d.ts +7 -0
  42. package/umd/src/conversion/parsePipeline/parsePreparedPipelineSections.d.ts +18 -0
  43. package/umd/src/conversion/parsePipeline/preparePipelineString.d.ts +8 -0
  44. package/umd/src/conversion/parsePipeline/processPipelineSection.d.ts +9 -0
  45. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -35,7 +35,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
35
35
  * @generated
36
36
  * @see https://github.com/webgptorg/promptbook
37
37
  */
38
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-71';
38
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
39
39
  /**
40
40
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
41
41
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -7608,118 +7608,75 @@ async function preparePipeline(pipeline, tools, options) {
7608
7608
  */
7609
7609
 
7610
7610
  /**
7611
- * All available task types
7612
- *
7613
- * There is is distinction between task types and section types
7614
- * - Every section in markdown has its SectionType
7615
- * - Some sections are tasks but other can be non-task sections
7616
- *
7617
- * @public exported from `@promptbook/core`
7618
- */
7619
- const TaskTypes = [
7620
- 'PROMPT',
7621
- 'SIMPLE',
7622
- 'SCRIPT',
7623
- 'DIALOG',
7624
- // <- [🅱]
7625
- ];
7626
-
7627
- /**
7628
- * All available sections which are not tasks
7629
- *
7630
- * @public exported from `@promptbook/core`
7631
- */
7632
- const NonTaskSectionTypes = ['EXAMPLE', 'KNOWLEDGE', 'INSTRUMENT', 'ACTION'];
7633
- /**
7634
- * All available section types
7635
- *
7636
- * There is is distinction between task types and section types
7637
- * - Every section in markdown has its SectionType
7638
- * - Some sections are tasks but other can be non-task sections
7611
+ * Parses the boilerplate command
7639
7612
  *
7640
- * @public exported from `@promptbook/core`
7641
- */
7642
- const SectionTypes = [
7643
- ...TaskTypes.map((TaskType) => `${TaskType}_TASK`),
7644
- ...NonTaskSectionTypes,
7645
- ];
7646
-
7647
- /**
7648
- * Parses the knowledge command
7613
+ * Note: @@ This command is used as boilerplate for new commands - it should NOT be used in any `.book` file
7649
7614
  *
7650
7615
  * @see `documentationUrl` for more details
7651
7616
  *
7652
- * @public exported from `@promptbook/editable`
7617
+ * @private within the commands folder
7653
7618
  */
7654
- const knowledgeCommandParser = {
7619
+ const boilerplateCommandParser = {
7655
7620
  /**
7656
7621
  * Name of the command
7657
7622
  */
7658
- name: 'KNOWLEDGE',
7623
+ name: 'BOILERPLATE',
7624
+ /**
7625
+ * Aliases for the BOILERPLATE command
7626
+ */
7627
+ aliasNames: ['BP'],
7659
7628
  /**
7660
7629
  * BOILERPLATE command can be used in:
7661
7630
  */
7662
7631
  isUsedInPipelineHead: true,
7663
- isUsedInPipelineTask: false,
7632
+ isUsedInPipelineTask: true,
7664
7633
  /**
7665
- * Description of the KNOWLEDGE command
7634
+ * Description of the BOILERPLATE command
7666
7635
  */
7667
- description: `Tells promptbook which external knowledge to use`,
7636
+ description: `@@`,
7668
7637
  /**
7669
7638
  * Link to documentation
7670
7639
  */
7671
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/41',
7640
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/@@',
7672
7641
  /**
7673
- * Example usages of the KNOWLEDGE command
7642
+ * Example usages of the BOILERPLATE command
7674
7643
  */
7675
- examples: [
7676
- 'KNOWLEDGE https://www.pavolhejny.com/',
7677
- 'KNOWLEDGE ./hejny-cv.txt',
7678
- 'KNOWLEDGE ./hejny-cv.md',
7679
- 'KNOWLEDGE ./hejny-cv.pdf',
7680
- 'KNOWLEDGE ./hejny-cv.docx',
7681
- // <- TODO: [😿] Allow ONLY files scoped in the (sub)directory NOT ../ and test it
7682
- ],
7644
+ examples: ['BOILERPLATE foo', 'BOILERPLATE bar', 'BP foo', 'BP bar'],
7683
7645
  /**
7684
- * Parses the KNOWLEDGE command
7646
+ * Parses the BOILERPLATE command
7685
7647
  */
7686
7648
  parse(input) {
7687
7649
  const { args } = input;
7688
- const knowledgeSourceContent = spaceTrim$1(args[0] || '');
7689
- if (knowledgeSourceContent === '') {
7690
- throw new ParseError(`Source is not defined`);
7691
- }
7692
- // TODO: [main] !!4 Following checks should be applied every link in the `sourceContent`
7693
- if (knowledgeSourceContent.startsWith('http://')) {
7694
- throw new ParseError(`Source is not secure`);
7695
- }
7696
- if (!(isValidFilePath(knowledgeSourceContent) || isValidUrl(knowledgeSourceContent))) {
7697
- throw new ParseError(`Source not valid`);
7650
+ if (args.length !== 1) {
7651
+ throw new ParseError(`BOILERPLATE command requires exactly one argument`);
7698
7652
  }
7699
- if (knowledgeSourceContent.startsWith('../') ||
7700
- knowledgeSourceContent.startsWith('/') ||
7701
- /^[A-Z]:[\\/]+/i.test(knowledgeSourceContent)) {
7702
- throw new ParseError(`Source cannot be outside of the .book.md folder`);
7653
+ const value = args[0].toLowerCase();
7654
+ if (value.includes('brr')) {
7655
+ throw new ParseError(`BOILERPLATE value can not contain brr`);
7703
7656
  }
7704
7657
  return {
7705
- type: 'KNOWLEDGE',
7706
- knowledgeSourceContent,
7658
+ type: 'BOILERPLATE',
7659
+ value,
7707
7660
  };
7708
7661
  },
7709
7662
  /**
7710
- * Apply the KNOWLEDGE command to the `pipelineJson`
7663
+ * Apply the BOILERPLATE command to the `pipelineJson`
7711
7664
  *
7712
7665
  * Note: `$` is used to indicate that this function mutates given `pipelineJson`
7713
7666
  */
7714
7667
  $applyToPipelineJson(command, $pipelineJson) {
7715
- const { knowledgeSourceContent } = command;
7716
- $pipelineJson.knowledgeSources.push({
7717
- name: knowledgeSourceContentToName(knowledgeSourceContent),
7718
- knowledgeSourceContent,
7719
- });
7668
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
7720
7669
  },
7721
7670
  /**
7722
- * Converts the KNOWLEDGE command back to string
7671
+ * Apply the BOILERPLATE command to the `pipelineJson`
7672
+ *
7673
+ * Note: `$` is used to indicate that this function mutates given `taskJson`
7674
+ */
7675
+ $applyToTaskJson(command, $taskJson, $pipelineJson) {
7676
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
7677
+ },
7678
+ /**
7679
+ * Converts the BOILERPLATE command back to string
7723
7680
  *
7724
7681
  * Note: This is used in `pipelineJsonToString` utility
7725
7682
  */
@@ -7727,354 +7684,77 @@ const knowledgeCommandParser = {
7727
7684
  return `---`; // <- TODO: [🛋] Implement
7728
7685
  },
7729
7686
  /**
7730
- * Reads the KNOWLEDGE command from the `PipelineJson`
7687
+ * Reads the BOILERPLATE command from the `PipelineJson`
7731
7688
  *
7732
7689
  * Note: This is used in `pipelineJsonToString` utility
7733
7690
  */
7734
7691
  takeFromPipelineJson(pipelineJson) {
7735
- throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
7692
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
7693
+ },
7694
+ /**
7695
+ * Reads the BOILERPLATE command from the `TaskJson`
7696
+ *
7697
+ * Note: This is used in `pipelineJsonToString` utility
7698
+ */
7699
+ takeFromTaskJson($taskJson) {
7700
+ throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
7736
7701
  },
7737
7702
  };
7738
- // Note: [⛱] There are two types of KNOWLEDGE commands *...(read more in [⛱])*
7739
7703
 
7740
7704
  /**
7741
- * Parses the section command
7705
+ * Parses the BOOK_VERSION command
7742
7706
  *
7743
7707
  * @see `documentationUrl` for more details
7744
7708
  *
7745
7709
  * @public exported from `@promptbook/editable`
7746
7710
  */
7747
- const sectionCommandParser = {
7711
+ const bookVersionCommandParser = {
7748
7712
  /**
7749
7713
  * Name of the command
7750
7714
  */
7751
- name: 'SECTION',
7752
- /**
7753
- * Aliases for the SECTION command
7754
- */
7755
- aliasNames: [
7756
- 'PROMPT',
7757
- 'SIMPLE',
7758
- 'SCRIPT',
7759
- 'DIALOG',
7760
- 'SAMPLE',
7761
- 'EXAMPLE',
7762
- 'KNOWLEDGE',
7763
- 'INSTRUMENT',
7764
- 'ACTION', // <- Note: [⛱]
7765
- ],
7766
- /**
7767
- * Aliases for the SECTION command
7768
- */
7769
- deprecatedNames: ['TEMPLATE', 'BLOCK', 'EXECUTE'],
7715
+ name: 'BOOK_VERSION',
7716
+ aliasNames: ['PTBK_VERSION', 'PROMPTBOOK_VERSION', 'BOOK'],
7770
7717
  /**
7771
7718
  * BOILERPLATE command can be used in:
7772
7719
  */
7773
- isUsedInPipelineHead: false,
7774
- isUsedInPipelineTask: true,
7720
+ isUsedInPipelineHead: true,
7721
+ isUsedInPipelineTask: false,
7775
7722
  /**
7776
- * Description of the SECTION command
7723
+ * Description of the BOOK_VERSION command
7777
7724
  */
7778
- description: `Defines the purpose of the markdown section - if its a task and which type or something else`,
7725
+ description: `Which version of the Book language is the .book.md using`,
7779
7726
  /**
7780
7727
  * Link to documentation
7781
7728
  */
7782
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/64',
7729
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/69',
7783
7730
  /**
7784
- * Example usages of the SECTION command
7731
+ * Example usages of the BOOK_VERSION command
7785
7732
  */
7786
- examples: [
7787
- // Short form:
7788
- 'PROMPT',
7789
- 'SIMPLE',
7790
- 'SCRIPT',
7791
- 'DIALOG',
7792
- // <- [🅱]
7793
- 'EXAMPLE',
7794
- 'KNOWLEDGE',
7795
- 'INSTRUMENT',
7796
- 'ACTION',
7797
- // -----------------
7798
- // Recommended (reversed) form:
7799
- 'PROMPT SECTION',
7800
- 'SIMPLE SECTION',
7801
- 'SCRIPT SECTION',
7802
- 'DIALOG SECTION',
7803
- // <- [🅱]
7804
- 'EXAMPLE SECTION',
7805
- 'KNOWLEDGE SECTION',
7806
- 'INSTRUMENT SECTION',
7807
- 'ACTION SECTION',
7808
- // -----------------
7809
- // Standard form:
7810
- 'SECTION PROMPT',
7811
- 'SECTION SIMPLE',
7812
- 'SECTION SCRIPT',
7813
- 'SECTION DIALOG',
7814
- // <- [🅱]
7815
- 'SECTION EXAMPLE',
7816
- 'SECTION KNOWLEDGE',
7817
- 'SECTION INSTRUMENT',
7818
- 'SECTION ACTION',
7819
- ],
7820
- // TODO: [♓️] order: -10 /* <- Note: Putting before other commands */
7733
+ examples: [`BOOK VERSION ${BOOK_LANGUAGE_VERSION}`, `BOOK ${BOOK_LANGUAGE_VERSION}`],
7821
7734
  /**
7822
- * Parses the SECTION command
7735
+ * Parses the BOOK_VERSION command
7823
7736
  */
7824
7737
  parse(input) {
7825
- let { normalized } = input;
7826
- normalized = normalized.split('SAMPLE').join('EXAMPLE');
7827
- normalized = normalized.split('EXECUTE_').join('');
7828
- normalized = normalized.split('DIALOGUE').join('DIALOG');
7829
- const taskTypes = SectionTypes.filter((sectionType) => normalized.includes(sectionType.split('_TASK').join('')));
7830
- if (taskTypes.length !== 1) {
7831
- throw new ParseError(spaceTrim$1((block) => `
7832
- Unknown section type "${normalized}"
7833
-
7834
- Supported section types are:
7835
- ${block(SectionTypes.join(', '))}
7836
- `));
7738
+ const { args } = input;
7739
+ const bookVersion = args.pop();
7740
+ if (bookVersion === undefined) {
7741
+ throw new ParseError(`Version is required`);
7742
+ }
7743
+ if (!isValidPromptbookVersion(bookVersion)) {
7744
+ throw new ParseError(`Invalid Promptbook version "${bookVersion}"`);
7745
+ }
7746
+ if (args.length > 0 && !(((args.length === 1 && args[0]) || '').toUpperCase() === 'VERSION')) {
7747
+ throw new ParseError(`Can not have more than one Promptbook version`);
7837
7748
  }
7838
- const taskType = taskTypes[0];
7839
7749
  return {
7840
- type: 'SECTION',
7841
- taskType,
7750
+ type: 'BOOK_VERSION',
7751
+ bookVersion: bookVersion,
7842
7752
  };
7843
7753
  },
7844
7754
  /**
7845
- * Apply the SECTION command to the `pipelineJson`
7755
+ * Apply the BOOK_VERSION command to the `pipelineJson`
7846
7756
  *
7847
- * Note: `$` is used to indicate that this function mutates given `taskJson`
7848
- */
7849
- $applyToTaskJson(command, $taskJson, $pipelineJson) {
7850
- if ($taskJson.isSectionTypeSet === true) {
7851
- throw new ParseError(spaceTrim$1(`
7852
- Section type is already defined in the section.
7853
- It can be defined only once.
7854
- `));
7855
- }
7856
- $taskJson.isSectionTypeSet = true;
7857
- // TODO: [🍧][💩] Rearrange better - but at bottom and unwrap from function
7858
- const expectResultingParameterName = () => {
7859
- if ($taskJson.resultingParameterName) {
7860
- return;
7861
- }
7862
- throw new ParseError(`Task section and example section must end with return statement -> {parameterName}`);
7863
- };
7864
- if ($taskJson.content === undefined) {
7865
- throw new UnexpectedError(`Content is missing in the taskJson - probably commands are applied in wrong order`);
7866
- }
7867
- if (command.taskType === 'EXAMPLE') {
7868
- expectResultingParameterName();
7869
- const parameter = $pipelineJson.parameters.find((param) => param.name === $taskJson.resultingParameterName);
7870
- if (parameter === undefined) {
7871
- // TODO: !!6 Change to logic error for higher level abstraction of chatbot to work
7872
- throw new ParseError(`Parameter \`{${$taskJson.resultingParameterName}}\` is not defined so can not define example value of it`);
7873
- }
7874
- parameter.exampleValues = parameter.exampleValues || [];
7875
- parameter.exampleValues.push($taskJson.content);
7876
- $taskJson.isTask = false;
7877
- return;
7878
- }
7879
- if (command.taskType === 'KNOWLEDGE') {
7880
- knowledgeCommandParser.$applyToPipelineJson({
7881
- type: 'KNOWLEDGE',
7882
- knowledgeSourceContent: $taskJson.content, // <- TODO: [🐝][main] !!3 Work with KNOWLEDGE which not referring to the source file or website, but its content itself
7883
- }, $pipelineJson);
7884
- $taskJson.isTask = false;
7885
- return;
7886
- }
7887
- if (command.taskType === 'ACTION') {
7888
- console.error(new NotYetImplementedError('Actions are not implemented yet'));
7889
- $taskJson.isTask = false;
7890
- return;
7891
- }
7892
- if (command.taskType === 'INSTRUMENT') {
7893
- console.error(new NotYetImplementedError('Instruments are not implemented yet'));
7894
- $taskJson.isTask = false;
7895
- return;
7896
- }
7897
- expectResultingParameterName();
7898
- $taskJson.taskType = command.taskType;
7899
- $taskJson.isTask = true;
7900
- },
7901
- /**
7902
- * Converts the SECTION command back to string
7903
- *
7904
- * Note: This is used in `pipelineJsonToString` utility
7905
- */
7906
- stringify(command) {
7907
- return `---`; // <- TODO: [🛋] Implement
7908
- },
7909
- /**
7910
- * Reads the SECTION command from the `TaskJson`
7911
- *
7912
- * Note: This is used in `pipelineJsonToString` utility
7913
- */
7914
- takeFromTaskJson($taskJson) {
7915
- throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
7916
- },
7917
- };
7918
- /**
7919
- * Note: [⛱] There are two types of KNOWLEDGE, ACTION and INSTRUMENT commands:
7920
- * 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
7921
- * - KNOWLEDGE Look at https://en.wikipedia.org/wiki/Artificial_intelligence
7922
- * 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
7923
- * - KNOWLEDGE SECTION
7924
- *
7925
- * ```
7926
- * Look at https://en.wikipedia.org/wiki/Artificial_intelligence
7927
- * ```
7928
- */
7929
-
7930
- /**
7931
- * Parses the boilerplate command
7932
- *
7933
- * Note: @@ This command is used as boilerplate for new commands - it should NOT be used in any `.book` file
7934
- *
7935
- * @see `documentationUrl` for more details
7936
- *
7937
- * @private within the commands folder
7938
- */
7939
- const boilerplateCommandParser = {
7940
- /**
7941
- * Name of the command
7942
- */
7943
- name: 'BOILERPLATE',
7944
- /**
7945
- * Aliases for the BOILERPLATE command
7946
- */
7947
- aliasNames: ['BP'],
7948
- /**
7949
- * BOILERPLATE command can be used in:
7950
- */
7951
- isUsedInPipelineHead: true,
7952
- isUsedInPipelineTask: true,
7953
- /**
7954
- * Description of the BOILERPLATE command
7955
- */
7956
- description: `@@`,
7957
- /**
7958
- * Link to documentation
7959
- */
7960
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/@@',
7961
- /**
7962
- * Example usages of the BOILERPLATE command
7963
- */
7964
- examples: ['BOILERPLATE foo', 'BOILERPLATE bar', 'BP foo', 'BP bar'],
7965
- /**
7966
- * Parses the BOILERPLATE command
7967
- */
7968
- parse(input) {
7969
- const { args } = input;
7970
- if (args.length !== 1) {
7971
- throw new ParseError(`BOILERPLATE command requires exactly one argument`);
7972
- }
7973
- const value = args[0].toLowerCase();
7974
- if (value.includes('brr')) {
7975
- throw new ParseError(`BOILERPLATE value can not contain brr`);
7976
- }
7977
- return {
7978
- type: 'BOILERPLATE',
7979
- value,
7980
- };
7981
- },
7982
- /**
7983
- * Apply the BOILERPLATE command to the `pipelineJson`
7984
- *
7985
- * Note: `$` is used to indicate that this function mutates given `pipelineJson`
7986
- */
7987
- $applyToPipelineJson(command, $pipelineJson) {
7988
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
7989
- },
7990
- /**
7991
- * Apply the BOILERPLATE command to the `pipelineJson`
7992
- *
7993
- * Note: `$` is used to indicate that this function mutates given `taskJson`
7994
- */
7995
- $applyToTaskJson(command, $taskJson, $pipelineJson) {
7996
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
7997
- },
7998
- /**
7999
- * Converts the BOILERPLATE command back to string
8000
- *
8001
- * Note: This is used in `pipelineJsonToString` utility
8002
- */
8003
- stringify(command) {
8004
- return `---`; // <- TODO: [🛋] Implement
8005
- },
8006
- /**
8007
- * Reads the BOILERPLATE command from the `PipelineJson`
8008
- *
8009
- * Note: This is used in `pipelineJsonToString` utility
8010
- */
8011
- takeFromPipelineJson(pipelineJson) {
8012
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
8013
- },
8014
- /**
8015
- * Reads the BOILERPLATE command from the `TaskJson`
8016
- *
8017
- * Note: This is used in `pipelineJsonToString` utility
8018
- */
8019
- takeFromTaskJson($taskJson) {
8020
- throw new ParseError(`BOILERPLATE command is only for testing purposes and should not be used in the .book.md file`);
8021
- },
8022
- };
8023
-
8024
- /**
8025
- * Parses the BOOK_VERSION command
8026
- *
8027
- * @see `documentationUrl` for more details
8028
- *
8029
- * @public exported from `@promptbook/editable`
8030
- */
8031
- const bookVersionCommandParser = {
8032
- /**
8033
- * Name of the command
8034
- */
8035
- name: 'BOOK_VERSION',
8036
- aliasNames: ['PTBK_VERSION', 'PROMPTBOOK_VERSION', 'BOOK'],
8037
- /**
8038
- * BOILERPLATE command can be used in:
8039
- */
8040
- isUsedInPipelineHead: true,
8041
- isUsedInPipelineTask: false,
8042
- /**
8043
- * Description of the BOOK_VERSION command
8044
- */
8045
- description: `Which version of the Book language is the .book.md using`,
8046
- /**
8047
- * Link to documentation
8048
- */
8049
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/69',
8050
- /**
8051
- * Example usages of the BOOK_VERSION command
8052
- */
8053
- examples: [`BOOK VERSION ${BOOK_LANGUAGE_VERSION}`, `BOOK ${BOOK_LANGUAGE_VERSION}`],
8054
- /**
8055
- * Parses the BOOK_VERSION command
8056
- */
8057
- parse(input) {
8058
- const { args } = input;
8059
- const bookVersion = args.pop();
8060
- if (bookVersion === undefined) {
8061
- throw new ParseError(`Version is required`);
8062
- }
8063
- if (!isValidPromptbookVersion(bookVersion)) {
8064
- throw new ParseError(`Invalid Promptbook version "${bookVersion}"`);
8065
- }
8066
- if (args.length > 0 && !(((args.length === 1 && args[0]) || '').toUpperCase() === 'VERSION')) {
8067
- throw new ParseError(`Can not have more than one Promptbook version`);
8068
- }
8069
- return {
8070
- type: 'BOOK_VERSION',
8071
- bookVersion: bookVersion,
8072
- };
8073
- },
8074
- /**
8075
- * Apply the BOOK_VERSION command to the `pipelineJson`
8076
- *
8077
- * Note: `$` is used to indicate that this function mutates given `pipelineJson`
7757
+ * Note: `$` is used to indicate that this function mutates given `pipelineJson`
8078
7758
  */
8079
7759
  $applyToPipelineJson(command, $pipelineJson) {
8080
7760
  // TODO: Warn if the version is overridden
@@ -9157,63 +8837,156 @@ const jokerCommandParser = {
9157
8837
  };
9158
8838
 
9159
8839
  /**
9160
- * Constant for model variants.
9161
- *
9162
- * @see {@link ModelVariant}
9163
- *
9164
- * @public exported from `@promptbook/core`
9165
- */
9166
- const MODEL_VARIANTS = [
9167
- 'COMPLETION',
9168
- 'CHAT',
9169
- 'IMAGE_GENERATION',
9170
- 'EMBEDDING' /* <- TODO [🏳] */ /* <- [🤖] */,
9171
- ];
9172
-
9173
- /**
9174
- * Parses the model command
8840
+ * Parses the knowledge command
9175
8841
  *
9176
8842
  * @see `documentationUrl` for more details
9177
- * @deprecated Option to manually set the model requirements is not recommended to use, use `PERSONA` instead
9178
8843
  *
9179
8844
  * @public exported from `@promptbook/editable`
9180
8845
  */
9181
- const modelCommandParser = {
8846
+ const knowledgeCommandParser = {
9182
8847
  /**
9183
8848
  * Name of the command
9184
8849
  */
9185
- name: 'MODEL',
8850
+ name: 'KNOWLEDGE',
9186
8851
  /**
9187
8852
  * BOILERPLATE command can be used in:
9188
8853
  */
9189
8854
  isUsedInPipelineHead: true,
9190
- isUsedInPipelineTask: true,
8855
+ isUsedInPipelineTask: false,
9191
8856
  /**
9192
- * Description of the MODEL command
8857
+ * Description of the KNOWLEDGE command
9193
8858
  */
9194
- description: `Tells which \`modelRequirements\` (for example which model) to use for the prompt task execution`,
8859
+ description: `Tells promptbook which external knowledge to use`,
9195
8860
  /**
9196
8861
  * Link to documentation
9197
8862
  */
9198
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/67',
8863
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/41',
9199
8864
  /**
9200
- * Example usages of the MODEL command
8865
+ * Example usages of the KNOWLEDGE command
9201
8866
  */
9202
- examples: ['MODEL VARIANT Chat', 'MODEL NAME `gpt-4`'],
8867
+ examples: [
8868
+ 'KNOWLEDGE https://www.pavolhejny.com/',
8869
+ 'KNOWLEDGE ./hejny-cv.txt',
8870
+ 'KNOWLEDGE ./hejny-cv.md',
8871
+ 'KNOWLEDGE ./hejny-cv.pdf',
8872
+ 'KNOWLEDGE ./hejny-cv.docx',
8873
+ // <- TODO: [😿] Allow ONLY files scoped in the (sub)directory NOT ../ and test it
8874
+ ],
9203
8875
  /**
9204
- * Parses the MODEL command
8876
+ * Parses the KNOWLEDGE command
9205
8877
  */
9206
8878
  parse(input) {
9207
- const { args, normalized } = input;
9208
- const availableVariantsMessage = spaceTrim$1((block) => `
9209
- Available variants are:
9210
- ${block(MODEL_VARIANTS.map((variantName) => `- ${variantName}${variantName !== 'EMBEDDING' ? '' : ' (Not available in pipeline)'}`).join('\n'))}
9211
- `);
9212
- // TODO: Make this more elegant and dynamically
9213
- if (normalized.startsWith('MODEL_VARIANT')) {
9214
- if (normalized === 'MODEL_VARIANT_CHAT') {
9215
- return {
9216
- type: 'MODEL',
8879
+ const { args } = input;
8880
+ const knowledgeSourceContent = spaceTrim$1(args[0] || '');
8881
+ if (knowledgeSourceContent === '') {
8882
+ throw new ParseError(`Source is not defined`);
8883
+ }
8884
+ // TODO: [main] !!4 Following checks should be applied every link in the `sourceContent`
8885
+ if (knowledgeSourceContent.startsWith('http://')) {
8886
+ throw new ParseError(`Source is not secure`);
8887
+ }
8888
+ if (!(isValidFilePath(knowledgeSourceContent) || isValidUrl(knowledgeSourceContent))) {
8889
+ throw new ParseError(`Source not valid`);
8890
+ }
8891
+ if (knowledgeSourceContent.startsWith('../') ||
8892
+ knowledgeSourceContent.startsWith('/') ||
8893
+ /^[A-Z]:[\\/]+/i.test(knowledgeSourceContent)) {
8894
+ throw new ParseError(`Source cannot be outside of the .book.md folder`);
8895
+ }
8896
+ return {
8897
+ type: 'KNOWLEDGE',
8898
+ knowledgeSourceContent,
8899
+ };
8900
+ },
8901
+ /**
8902
+ * Apply the KNOWLEDGE command to the `pipelineJson`
8903
+ *
8904
+ * Note: `$` is used to indicate that this function mutates given `pipelineJson`
8905
+ */
8906
+ $applyToPipelineJson(command, $pipelineJson) {
8907
+ const { knowledgeSourceContent } = command;
8908
+ $pipelineJson.knowledgeSources.push({
8909
+ name: knowledgeSourceContentToName(knowledgeSourceContent),
8910
+ knowledgeSourceContent,
8911
+ });
8912
+ },
8913
+ /**
8914
+ * Converts the KNOWLEDGE command back to string
8915
+ *
8916
+ * Note: This is used in `pipelineJsonToString` utility
8917
+ */
8918
+ stringify(command) {
8919
+ return `---`; // <- TODO: [🛋] Implement
8920
+ },
8921
+ /**
8922
+ * Reads the KNOWLEDGE command from the `PipelineJson`
8923
+ *
8924
+ * Note: This is used in `pipelineJsonToString` utility
8925
+ */
8926
+ takeFromPipelineJson(pipelineJson) {
8927
+ throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
8928
+ },
8929
+ };
8930
+ // Note: [⛱] There are two types of KNOWLEDGE commands *...(read more in [⛱])*
8931
+
8932
+ /**
8933
+ * Constant for model variants.
8934
+ *
8935
+ * @see {@link ModelVariant}
8936
+ *
8937
+ * @public exported from `@promptbook/core`
8938
+ */
8939
+ const MODEL_VARIANTS = [
8940
+ 'COMPLETION',
8941
+ 'CHAT',
8942
+ 'IMAGE_GENERATION',
8943
+ 'EMBEDDING' /* <- TODO [🏳] */ /* <- [🤖] */,
8944
+ ];
8945
+
8946
+ /**
8947
+ * Parses the model command
8948
+ *
8949
+ * @see `documentationUrl` for more details
8950
+ * @deprecated Option to manually set the model requirements is not recommended to use, use `PERSONA` instead
8951
+ *
8952
+ * @public exported from `@promptbook/editable`
8953
+ */
8954
+ const modelCommandParser = {
8955
+ /**
8956
+ * Name of the command
8957
+ */
8958
+ name: 'MODEL',
8959
+ /**
8960
+ * BOILERPLATE command can be used in:
8961
+ */
8962
+ isUsedInPipelineHead: true,
8963
+ isUsedInPipelineTask: true,
8964
+ /**
8965
+ * Description of the MODEL command
8966
+ */
8967
+ description: `Tells which \`modelRequirements\` (for example which model) to use for the prompt task execution`,
8968
+ /**
8969
+ * Link to documentation
8970
+ */
8971
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/67',
8972
+ /**
8973
+ * Example usages of the MODEL command
8974
+ */
8975
+ examples: ['MODEL VARIANT Chat', 'MODEL NAME `gpt-4`'],
8976
+ /**
8977
+ * Parses the MODEL command
8978
+ */
8979
+ parse(input) {
8980
+ const { args, normalized } = input;
8981
+ const availableVariantsMessage = spaceTrim$1((block) => `
8982
+ Available variants are:
8983
+ ${block(MODEL_VARIANTS.map((variantName) => `- ${variantName}${variantName !== 'EMBEDDING' ? '' : ' (Not available in pipeline)'}`).join('\n'))}
8984
+ `);
8985
+ // TODO: Make this more elegant and dynamically
8986
+ if (normalized.startsWith('MODEL_VARIANT')) {
8987
+ if (normalized === 'MODEL_VARIANT_CHAT') {
8988
+ return {
8989
+ type: 'MODEL',
9217
8990
  key: 'modelVariant',
9218
8991
  value: 'CHAT',
9219
8992
  };
@@ -9690,69 +9463,296 @@ const postprocessCommandParser = {
9690
9463
  };
9691
9464
 
9692
9465
  /**
9693
- * Parses the url command
9466
+ * All available task types
9467
+ *
9468
+ * There is is distinction between task types and section types
9469
+ * - Every section in markdown has its SectionType
9470
+ * - Some sections are tasks but other can be non-task sections
9471
+ *
9472
+ * @public exported from `@promptbook/core`
9473
+ */
9474
+ const TaskTypes = [
9475
+ 'PROMPT',
9476
+ 'SIMPLE',
9477
+ 'SCRIPT',
9478
+ 'DIALOG',
9479
+ // <- [🅱]
9480
+ ];
9481
+
9482
+ /**
9483
+ * All available sections which are not tasks
9484
+ *
9485
+ * @public exported from `@promptbook/core`
9486
+ */
9487
+ const NonTaskSectionTypes = ['EXAMPLE', 'KNOWLEDGE', 'INSTRUMENT', 'ACTION'];
9488
+ /**
9489
+ * All available section types
9490
+ *
9491
+ * There is is distinction between task types and section types
9492
+ * - Every section in markdown has its SectionType
9493
+ * - Some sections are tasks but other can be non-task sections
9494
+ *
9495
+ * @public exported from `@promptbook/core`
9496
+ */
9497
+ const SectionTypes = [
9498
+ ...TaskTypes.map((TaskType) => `${TaskType}_TASK`),
9499
+ ...NonTaskSectionTypes,
9500
+ ];
9501
+
9502
+ /**
9503
+ * Parses the section command
9694
9504
  *
9695
9505
  * @see `documentationUrl` for more details
9696
9506
  *
9697
9507
  * @public exported from `@promptbook/editable`
9698
9508
  */
9699
- const urlCommandParser = {
9509
+ const sectionCommandParser = {
9700
9510
  /**
9701
9511
  * Name of the command
9702
9512
  */
9703
- name: 'URL',
9704
- aliasNames: ['PIPELINE_URL'],
9705
- /*
9706
- Note: [🛵] No need for this alias name because it is already preprocessed
9707
- aliasNames: ['HTTPS'],
9708
- */
9513
+ name: 'SECTION',
9514
+ /**
9515
+ * Aliases for the SECTION command
9516
+ */
9517
+ aliasNames: [
9518
+ 'PROMPT',
9519
+ 'SIMPLE',
9520
+ 'SCRIPT',
9521
+ 'DIALOG',
9522
+ 'SAMPLE',
9523
+ 'EXAMPLE',
9524
+ 'KNOWLEDGE',
9525
+ 'INSTRUMENT',
9526
+ 'ACTION', // <- Note: [⛱]
9527
+ ],
9528
+ /**
9529
+ * Aliases for the SECTION command
9530
+ */
9531
+ deprecatedNames: ['TEMPLATE', 'BLOCK', 'EXECUTE'],
9709
9532
  /**
9710
9533
  * BOILERPLATE command can be used in:
9711
9534
  */
9712
- isUsedInPipelineHead: true,
9713
- isUsedInPipelineTask: false,
9535
+ isUsedInPipelineHead: false,
9536
+ isUsedInPipelineTask: true,
9714
9537
  /**
9715
- * Description of the URL command
9538
+ * Description of the SECTION command
9716
9539
  */
9717
- description: `Declares unique URL for the pipeline`,
9540
+ description: `Defines the purpose of the markdown section - if its a task and which type or something else`,
9718
9541
  /**
9719
9542
  * Link to documentation
9720
9543
  */
9721
- documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/70',
9544
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/64',
9722
9545
  /**
9723
- * Example usages of the URL command
9546
+ * Example usages of the SECTION command
9724
9547
  */
9725
9548
  examples: [
9726
- 'PIPELINE URL https://promptbook.studio/library/write-cv.book',
9727
- 'URL https://promptbook.studio/library/write-cv.book',
9728
- 'https://promptbook.studio/library/write-cv.book',
9549
+ // Short form:
9550
+ 'PROMPT',
9551
+ 'SIMPLE',
9552
+ 'SCRIPT',
9553
+ 'DIALOG',
9554
+ // <- [🅱]
9555
+ 'EXAMPLE',
9556
+ 'KNOWLEDGE',
9557
+ 'INSTRUMENT',
9558
+ 'ACTION',
9559
+ // -----------------
9560
+ // Recommended (reversed) form:
9561
+ 'PROMPT SECTION',
9562
+ 'SIMPLE SECTION',
9563
+ 'SCRIPT SECTION',
9564
+ 'DIALOG SECTION',
9565
+ // <- [🅱]
9566
+ 'EXAMPLE SECTION',
9567
+ 'KNOWLEDGE SECTION',
9568
+ 'INSTRUMENT SECTION',
9569
+ 'ACTION SECTION',
9570
+ // -----------------
9571
+ // Standard form:
9572
+ 'SECTION PROMPT',
9573
+ 'SECTION SIMPLE',
9574
+ 'SECTION SCRIPT',
9575
+ 'SECTION DIALOG',
9576
+ // <- [🅱]
9577
+ 'SECTION EXAMPLE',
9578
+ 'SECTION KNOWLEDGE',
9579
+ 'SECTION INSTRUMENT',
9580
+ 'SECTION ACTION',
9729
9581
  ],
9582
+ // TODO: [♓️] order: -10 /* <- Note: Putting before other commands */
9730
9583
  /**
9731
- * Parses the URL command
9584
+ * Parses the SECTION command
9732
9585
  */
9733
9586
  parse(input) {
9734
- const { args } = input;
9735
- const pipelineUrl = args.pop();
9736
- if (pipelineUrl === undefined) {
9737
- throw new ParseError(`URL is required`);
9738
- }
9739
- // TODO: [🧠][🚲] This should be maybe tested as logic not parse
9740
- if (!isValidPipelineUrl(pipelineUrl)) {
9741
- throw new ParseError(`Invalid pipeline URL "${pipelineUrl}"`);
9742
- }
9743
- if (args.length > 0) {
9744
- throw new ParseError(`Can not have more than one pipeline URL`);
9745
- }
9746
- /*
9747
- TODO: [🐠 Maybe more info from `isValidPipelineUrl`:
9748
- if (pipelineUrl.protocol !== 'https:') {
9749
- throw new ParseError(`Protocol must be HTTPS`);
9750
- }
9587
+ let { normalized } = input;
9588
+ normalized = normalized.split('SAMPLE').join('EXAMPLE');
9589
+ normalized = normalized.split('EXECUTE_').join('');
9590
+ normalized = normalized.split('DIALOGUE').join('DIALOG');
9591
+ const taskTypes = SectionTypes.filter((sectionType) => normalized.includes(sectionType.split('_TASK').join('')));
9592
+ if (taskTypes.length !== 1) {
9593
+ throw new ParseError(spaceTrim$1((block) => `
9594
+ Unknown section type "${normalized}"
9751
9595
 
9752
- if (pipelineUrl.hash !== '') {
9753
- throw new ParseError(
9754
- spaceTrim(
9755
- `
9596
+ Supported section types are:
9597
+ ${block(SectionTypes.join(', '))}
9598
+ `));
9599
+ }
9600
+ const taskType = taskTypes[0];
9601
+ return {
9602
+ type: 'SECTION',
9603
+ taskType,
9604
+ };
9605
+ },
9606
+ /**
9607
+ * Apply the SECTION command to the `pipelineJson`
9608
+ *
9609
+ * Note: `$` is used to indicate that this function mutates given `taskJson`
9610
+ */
9611
+ $applyToTaskJson(command, $taskJson, $pipelineJson) {
9612
+ if ($taskJson.isSectionTypeSet === true) {
9613
+ throw new ParseError(spaceTrim$1(`
9614
+ Section type is already defined in the section.
9615
+ It can be defined only once.
9616
+ `));
9617
+ }
9618
+ $taskJson.isSectionTypeSet = true;
9619
+ // TODO: [🍧][💩] Rearrange better - but at bottom and unwrap from function
9620
+ const expectResultingParameterName = () => {
9621
+ if ($taskJson.resultingParameterName) {
9622
+ return;
9623
+ }
9624
+ throw new ParseError(`Task section and example section must end with return statement -> {parameterName}`);
9625
+ };
9626
+ if ($taskJson.content === undefined) {
9627
+ throw new UnexpectedError(`Content is missing in the taskJson - probably commands are applied in wrong order`);
9628
+ }
9629
+ if (command.taskType === 'EXAMPLE') {
9630
+ expectResultingParameterName();
9631
+ const parameter = $pipelineJson.parameters.find((param) => param.name === $taskJson.resultingParameterName);
9632
+ if (parameter === undefined) {
9633
+ // TODO: !!6 Change to logic error for higher level abstraction of chatbot to work
9634
+ throw new ParseError(`Parameter \`{${$taskJson.resultingParameterName}}\` is not defined so can not define example value of it`);
9635
+ }
9636
+ parameter.exampleValues = parameter.exampleValues || [];
9637
+ parameter.exampleValues.push($taskJson.content);
9638
+ $taskJson.isTask = false;
9639
+ return;
9640
+ }
9641
+ if (command.taskType === 'KNOWLEDGE') {
9642
+ knowledgeCommandParser.$applyToPipelineJson({
9643
+ type: 'KNOWLEDGE',
9644
+ knowledgeSourceContent: $taskJson.content, // <- TODO: [🐝][main] !!3 Work with KNOWLEDGE which not referring to the source file or website, but its content itself
9645
+ }, $pipelineJson);
9646
+ $taskJson.isTask = false;
9647
+ return;
9648
+ }
9649
+ if (command.taskType === 'ACTION') {
9650
+ console.error(new NotYetImplementedError('Actions are not implemented yet'));
9651
+ $taskJson.isTask = false;
9652
+ return;
9653
+ }
9654
+ if (command.taskType === 'INSTRUMENT') {
9655
+ console.error(new NotYetImplementedError('Instruments are not implemented yet'));
9656
+ $taskJson.isTask = false;
9657
+ return;
9658
+ }
9659
+ expectResultingParameterName();
9660
+ $taskJson.taskType = command.taskType;
9661
+ $taskJson.isTask = true;
9662
+ },
9663
+ /**
9664
+ * Converts the SECTION command back to string
9665
+ *
9666
+ * Note: This is used in `pipelineJsonToString` utility
9667
+ */
9668
+ stringify(command) {
9669
+ return `---`; // <- TODO: [🛋] Implement
9670
+ },
9671
+ /**
9672
+ * Reads the SECTION command from the `TaskJson`
9673
+ *
9674
+ * Note: This is used in `pipelineJsonToString` utility
9675
+ */
9676
+ takeFromTaskJson($taskJson) {
9677
+ throw new NotYetImplementedError(`[🛋] Not implemented yet`); // <- TODO: [🛋] Implement
9678
+ },
9679
+ };
9680
+ /**
9681
+ * Note: [⛱] There are two types of KNOWLEDGE, ACTION and INSTRUMENT commands:
9682
+ * 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
9683
+ * - KNOWLEDGE Look at https://en.wikipedia.org/wiki/Artificial_intelligence
9684
+ * 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
9685
+ * - KNOWLEDGE SECTION
9686
+ *
9687
+ * ```
9688
+ * Look at https://en.wikipedia.org/wiki/Artificial_intelligence
9689
+ * ```
9690
+ */
9691
+
9692
+ /**
9693
+ * Parses the url command
9694
+ *
9695
+ * @see `documentationUrl` for more details
9696
+ *
9697
+ * @public exported from `@promptbook/editable`
9698
+ */
9699
+ const urlCommandParser = {
9700
+ /**
9701
+ * Name of the command
9702
+ */
9703
+ name: 'URL',
9704
+ aliasNames: ['PIPELINE_URL'],
9705
+ /*
9706
+ Note: [🛵] No need for this alias name because it is already preprocessed
9707
+ aliasNames: ['HTTPS'],
9708
+ */
9709
+ /**
9710
+ * BOILERPLATE command can be used in:
9711
+ */
9712
+ isUsedInPipelineHead: true,
9713
+ isUsedInPipelineTask: false,
9714
+ /**
9715
+ * Description of the URL command
9716
+ */
9717
+ description: `Declares unique URL for the pipeline`,
9718
+ /**
9719
+ * Link to documentation
9720
+ */
9721
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/70',
9722
+ /**
9723
+ * Example usages of the URL command
9724
+ */
9725
+ examples: [
9726
+ 'PIPELINE URL https://promptbook.studio/library/write-cv.book',
9727
+ 'URL https://promptbook.studio/library/write-cv.book',
9728
+ 'https://promptbook.studio/library/write-cv.book',
9729
+ ],
9730
+ /**
9731
+ * Parses the URL command
9732
+ */
9733
+ parse(input) {
9734
+ const { args } = input;
9735
+ const pipelineUrl = args.pop();
9736
+ if (pipelineUrl === undefined) {
9737
+ throw new ParseError(`URL is required`);
9738
+ }
9739
+ // TODO: [🧠][🚲] This should be maybe tested as logic not parse
9740
+ if (!isValidPipelineUrl(pipelineUrl)) {
9741
+ throw new ParseError(`Invalid pipeline URL "${pipelineUrl}"`);
9742
+ }
9743
+ if (args.length > 0) {
9744
+ throw new ParseError(`Can not have more than one pipeline URL`);
9745
+ }
9746
+ /*
9747
+ TODO: [🐠 Maybe more info from `isValidPipelineUrl`:
9748
+ if (pipelineUrl.protocol !== 'https:') {
9749
+ throw new ParseError(`Protocol must be HTTPS`);
9750
+ }
9751
+
9752
+ if (pipelineUrl.hash !== '') {
9753
+ throw new ParseError(
9754
+ spaceTrim(
9755
+ `
9756
9756
  URL must not contain hash
9757
9757
  Hash is used for identification of the section of the pipeline
9758
9758
  `,
@@ -10143,85 +10143,321 @@ function parseCommandVariant(input) {
10143
10143
  }
10144
10144
 
10145
10145
  /**
10146
- * Extracts the interface (input and output parameters) from a pipeline.
10146
+ * Utility function to extract all list items from markdown
10147
10147
  *
10148
- * @deprecated https://github.com/webgptorg/promptbook/pull/186
10149
- * @see https://github.com/webgptorg/promptbook/discussions/171
10148
+ * Note: It works with both ul and ol
10149
+ * Note: It omits list items in code blocks
10150
+ * Note: It flattens nested lists
10151
+ * Note: It can not work with html syntax and comments
10150
10152
  *
10151
- * @public exported from `@promptbook/core`
10153
+ * @param markdown any valid markdown
10154
+ * @returns An array of strings, each representing an individual list item found in the markdown
10155
+ *
10156
+ * @public exported from `@promptbook/markdown-utils`
10152
10157
  */
10153
- function getPipelineInterface(pipeline) {
10154
- const pipelineInterface = {
10155
- inputParameters: [],
10156
- outputParameters: [],
10157
- };
10158
- for (const parameter of pipeline.parameters) {
10159
- const { isInput, isOutput } = parameter;
10160
- if (isInput) {
10161
- pipelineInterface.inputParameters.push(deepClone(parameter));
10158
+ function extractAllListItemsFromMarkdown(markdown) {
10159
+ const lines = markdown.split(/\r?\n/);
10160
+ const listItems = [];
10161
+ let isInCodeBlock = false;
10162
+ for (const line of lines) {
10163
+ const trimmedLine = line.trim();
10164
+ if (trimmedLine.startsWith('```')) {
10165
+ isInCodeBlock = !isInCodeBlock;
10162
10166
  }
10163
- if (isOutput) {
10164
- pipelineInterface.outputParameters.push(deepClone(parameter));
10167
+ if (!isInCodeBlock && (trimmedLine.startsWith('-') || trimmedLine.match(/^\d+\./))) {
10168
+ const listItem = trimmedLine.replace(/^-|\d+\./, '').trim();
10169
+ listItems.push(listItem);
10165
10170
  }
10166
10171
  }
10167
- for (const key of ['inputParameters', 'outputParameters']) {
10168
- pipelineInterface[key].sort(({ name: name1 }, { name: name2 }) => name1.localeCompare(name2));
10169
- }
10170
- return exportJson({
10171
- name: `pipelineInterface`,
10172
- message: `Result of \`getPipelineInterface\``,
10173
- order: ['inputParameters', 'outputParameters'],
10174
- value: pipelineInterface,
10175
- });
10172
+ return listItems;
10176
10173
  }
10177
10174
 
10178
10175
  /**
10179
- * Checks if two pipeline interfaces are structurally identical.
10180
- *
10181
- * @deprecated https://github.com/webgptorg/promptbook/pull/186
10182
- * @see https://github.com/webgptorg/promptbook/discussions/171
10176
+ * Builds a short file/url identification block for parse errors.
10183
10177
  *
10184
- * @public exported from `@promptbook/core`
10178
+ * @private internal utility of `parsePipeline`
10185
10179
  */
10186
- function isPipelineInterfacesEqual(pipelineInterface1, pipelineInterface2) {
10187
- for (const whichParameters of ['inputParameters', 'outputParameters']) {
10188
- const parameters1 = pipelineInterface1[whichParameters]; // <- Note: `isPipelineInterfacesEqual` is just temporary solution, no need to fix this
10189
- const parameters2 = pipelineInterface2[whichParameters];
10190
- if (parameters1.length !== parameters2.length) {
10191
- return false;
10192
- }
10193
- for (const parameter of parameters1) {
10194
- const matchingParameter = parameters2.find(({ name }) => name === parameter.name);
10195
- if (!matchingParameter) {
10196
- return false;
10197
- }
10198
- // Note: Do not compare description, it is not relevant for compatibility
10199
- if (matchingParameter.isInput !== parameter.isInput) {
10200
- return false;
10201
- }
10202
- if (matchingParameter.isOutput !== parameter.isOutput) {
10203
- return false;
10204
- }
10205
- }
10180
+ function getPipelineIdentification($pipelineJson) {
10181
+ // Note: This is a 😐 implementation of [🚞]
10182
+ const pipelineIdentificationParts = [];
10183
+ if ($pipelineJson.sourceFile !== undefined) {
10184
+ pipelineIdentificationParts.push(`File: ${$pipelineJson.sourceFile}`);
10206
10185
  }
10207
- return true;
10186
+ if ($pipelineJson.pipelineUrl !== undefined) {
10187
+ pipelineIdentificationParts.push(`Url: ${$pipelineJson.pipelineUrl}`);
10188
+ }
10189
+ return pipelineIdentificationParts.join('\n');
10208
10190
  }
10209
10191
 
10210
10192
  /**
10211
- * Checks if a given pipeline satisfies the requirements of a specified pipeline interface.
10212
- *
10213
- * @deprecated https://github.com/webgptorg/promptbook/pull/186
10214
- * @see https://github.com/webgptorg/promptbook/discussions/171
10215
- * @returns `true` if the pipeline implements the interface, `false` otherwise.
10193
+ * Merges one parameter declaration into the mutable pipeline parameter list.
10216
10194
  *
10217
- * @public exported from `@promptbook/core`
10195
+ * @private internal utility of `parsePipeline`
10218
10196
  */
10219
- function isPipelineImplementingInterface(options) {
10220
- const { pipeline, pipelineInterface } = options;
10221
- return isPipelineInterfacesEqual(getPipelineInterface(pipeline), pipelineInterface);
10222
- }
10223
-
10224
- /**
10197
+ function defineParameter($pipelineJson, parameterCommand) {
10198
+ const { parameterName, parameterDescription, isInput, isOutput } = parameterCommand;
10199
+ if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
10200
+ throw new ParseError(spaceTrim$1((block) => `
10201
+ Parameter name {${parameterName}} is reserved and cannot be used as resulting parameter name
10202
+
10203
+ ${block(getPipelineIdentification($pipelineJson))}
10204
+ `) /* <- TODO: [🚞] */);
10205
+ }
10206
+ const existingParameter = $pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
10207
+ if (existingParameter &&
10208
+ existingParameter.description &&
10209
+ existingParameter.description !== parameterDescription &&
10210
+ parameterDescription) {
10211
+ throw new ParseError(spaceTrim$1((block) => `
10212
+ Parameter \`{${parameterName}}\` is defined multiple times with different description:
10213
+
10214
+ ${block(getPipelineIdentification($pipelineJson))}
10215
+
10216
+ First definition:
10217
+ ${block(existingParameter.description || '[undefined]')}
10218
+
10219
+ Second definition:
10220
+ ${block(parameterDescription || '[undefined]')}
10221
+ `));
10222
+ }
10223
+ if (existingParameter) {
10224
+ if (parameterDescription) {
10225
+ existingParameter.description = parameterDescription;
10226
+ }
10227
+ existingParameter.isInput = existingParameter.isInput || isInput;
10228
+ existingParameter.isOutput = existingParameter.isOutput || isOutput;
10229
+ return;
10230
+ }
10231
+ $pipelineJson.parameters.push({
10232
+ name: parameterName,
10233
+ description: parameterDescription || undefined,
10234
+ isInput,
10235
+ isOutput,
10236
+ });
10237
+ }
10238
+
10239
+ /**
10240
+ * Removes fenced code blocks when deriving human-readable section descriptions.
10241
+ *
10242
+ * @private internal utility of `extractPipelineDescription`
10243
+ */
10244
+ const DESCRIPTION_CODE_BLOCK_REGEXP = /^```.*^```/gms;
10245
+ /**
10246
+ * Removes blockquote lines when deriving human-readable section descriptions.
10247
+ *
10248
+ * @private internal utility of `extractPipelineDescription`
10249
+ */
10250
+ const DESCRIPTION_BLOCKQUOTE_REGEXP = /^>.*$/gm;
10251
+ /**
10252
+ * Removes list items and return statements when deriving human-readable section descriptions.
10253
+ *
10254
+ * @private internal utility of `extractPipelineDescription`
10255
+ */
10256
+ const DESCRIPTION_LIST_ITEM_REGEXP = /^(?:(?:-)|(?:\d\))|(?:`?->))\s+.*$/gm;
10257
+ /**
10258
+ * Extracts the plain-text description from a head or task section body.
10259
+ *
10260
+ * @private internal utility of `parsePipeline`
10261
+ */
10262
+ function extractPipelineDescription(sectionContent) {
10263
+ let description = sectionContent;
10264
+ description = description.split(DESCRIPTION_CODE_BLOCK_REGEXP).join('');
10265
+ description = description.split(DESCRIPTION_BLOCKQUOTE_REGEXP).join('');
10266
+ description = description.split(DESCRIPTION_LIST_ITEM_REGEXP).join('');
10267
+ description = spaceTrim$1(description);
10268
+ if (description === '') {
10269
+ return undefined;
10270
+ }
10271
+ return description;
10272
+ }
10273
+
10274
+ /**
10275
+ * Applies the pipeline head title, description, and head-level commands.
10276
+ *
10277
+ * @private internal utility of `parsePipeline`
10278
+ */
10279
+ function applyPipelineHead(pipelineHead, $pipelineJson) {
10280
+ $pipelineJson.title = pipelineHead.title;
10281
+ $pipelineJson.description = extractPipelineDescription(pipelineHead.content);
10282
+ for (const listItem of extractAllListItemsFromMarkdown(pipelineHead.content)) {
10283
+ applyPipelineHeadCommand(listItem, $pipelineJson);
10284
+ }
10285
+ }
10286
+ /**
10287
+ * Parses and applies one command declared in the pipeline head.
10288
+ *
10289
+ * @private internal utility of `applyPipelineHead`
10290
+ */
10291
+ function applyPipelineHeadCommand(listItem, $pipelineJson) {
10292
+ const command = parseCommand(listItem, 'PIPELINE_HEAD');
10293
+ const commandParser = getParserForCommand(command);
10294
+ if (commandParser.isUsedInPipelineHead !== true /* <- Note: [🦦][4] */) {
10295
+ throw new ParseError(spaceTrim$1((block) => `
10296
+ Command \`${command.type}\` is not allowed in the head of the pipeline ONLY at the pipeline task
10297
+
10298
+ ${block(getPipelineIdentification($pipelineJson))}
10299
+ `)); // <- TODO: [🚞]
10300
+ }
10301
+ try {
10302
+ commandParser.$applyToPipelineJson(command, $pipelineJson);
10303
+ // <- Note: [🦦] Its strange that this assertion must be here, [🦦][4] should do this assertion implicitly
10304
+ }
10305
+ catch (error) {
10306
+ if (!(error instanceof ParseError)) {
10307
+ throw error;
10308
+ }
10309
+ throw new ParseError(spaceTrim$1((block) => `
10310
+ Command ${command.type} failed to apply to the pipeline
10311
+
10312
+ The error:
10313
+ ${block(error.message)}
10314
+
10315
+ Raw command:
10316
+ - ${listItem}
10317
+
10318
+ Usage of ${command.type}:
10319
+ ${block(commandParser.examples.map((example) => `- ${example}`).join('\n'))}
10320
+
10321
+ ${block(getPipelineIdentification($pipelineJson))}
10322
+ `)); // <- TODO: [🚞]
10323
+ }
10324
+ if (command.type === 'PARAMETER') {
10325
+ defineParameter($pipelineJson, command);
10326
+ // <- Note: [🍣]
10327
+ }
10328
+ }
10329
+
10330
+ /**
10331
+ * Creates the mutable pipeline JSON structure used throughout parsing.
10332
+ *
10333
+ * @private internal utility of `parsePipeline`
10334
+ */
10335
+ function createInitialPipelineJson(pipelineString) {
10336
+ return {
10337
+ title: DEFAULT_BOOK_TITLE,
10338
+ parameters: [],
10339
+ tasks: [],
10340
+ knowledgeSources: [],
10341
+ knowledgePieces: [],
10342
+ personas: [],
10343
+ preparations: [],
10344
+ sources: [
10345
+ {
10346
+ type: 'BOOK',
10347
+ path: null,
10348
+ // <- TODO: !!6 Pass here path of the file
10349
+ content: pipelineString,
10350
+ },
10351
+ ],
10352
+ };
10353
+ }
10354
+
10355
+ /**
10356
+ * Creates stable unique task names for duplicate section titles.
10357
+ *
10358
+ * @private internal utility of `parsePipeline`
10359
+ */
10360
+ function createUniqueSectionNameResolver(pipelineSections) {
10361
+ const sectionCounts = {};
10362
+ for (const pipelineSection of pipelineSections) {
10363
+ const sectionName = titleToName(pipelineSection.title);
10364
+ if (sectionCounts[sectionName] === undefined) {
10365
+ sectionCounts[sectionName] = { count: 0, currentIndex: 0 };
10366
+ }
10367
+ sectionCounts[sectionName].count++;
10368
+ }
10369
+ return (title) => {
10370
+ const sectionName = titleToName(title);
10371
+ const sectionCount = sectionCounts[sectionName];
10372
+ if (sectionCount.count === 1) {
10373
+ return sectionName;
10374
+ }
10375
+ const nameWithSuffix = `${sectionName}-${sectionCount.currentIndex}`;
10376
+ sectionCount.currentIndex++;
10377
+ return nameWithSuffix;
10378
+ };
10379
+ }
10380
+
10381
+ /**
10382
+ * Extracts the interface (input and output parameters) from a pipeline.
10383
+ *
10384
+ * @deprecated https://github.com/webgptorg/promptbook/pull/186
10385
+ * @see https://github.com/webgptorg/promptbook/discussions/171
10386
+ *
10387
+ * @public exported from `@promptbook/core`
10388
+ */
10389
+ function getPipelineInterface(pipeline) {
10390
+ const pipelineInterface = {
10391
+ inputParameters: [],
10392
+ outputParameters: [],
10393
+ };
10394
+ for (const parameter of pipeline.parameters) {
10395
+ const { isInput, isOutput } = parameter;
10396
+ if (isInput) {
10397
+ pipelineInterface.inputParameters.push(deepClone(parameter));
10398
+ }
10399
+ if (isOutput) {
10400
+ pipelineInterface.outputParameters.push(deepClone(parameter));
10401
+ }
10402
+ }
10403
+ for (const key of ['inputParameters', 'outputParameters']) {
10404
+ pipelineInterface[key].sort(({ name: name1 }, { name: name2 }) => name1.localeCompare(name2));
10405
+ }
10406
+ return exportJson({
10407
+ name: `pipelineInterface`,
10408
+ message: `Result of \`getPipelineInterface\``,
10409
+ order: ['inputParameters', 'outputParameters'],
10410
+ value: pipelineInterface,
10411
+ });
10412
+ }
10413
+
10414
+ /**
10415
+ * Checks if two pipeline interfaces are structurally identical.
10416
+ *
10417
+ * @deprecated https://github.com/webgptorg/promptbook/pull/186
10418
+ * @see https://github.com/webgptorg/promptbook/discussions/171
10419
+ *
10420
+ * @public exported from `@promptbook/core`
10421
+ */
10422
+ function isPipelineInterfacesEqual(pipelineInterface1, pipelineInterface2) {
10423
+ for (const whichParameters of ['inputParameters', 'outputParameters']) {
10424
+ const parameters1 = pipelineInterface1[whichParameters]; // <- Note: `isPipelineInterfacesEqual` is just temporary solution, no need to fix this
10425
+ const parameters2 = pipelineInterface2[whichParameters];
10426
+ if (parameters1.length !== parameters2.length) {
10427
+ return false;
10428
+ }
10429
+ for (const parameter of parameters1) {
10430
+ const matchingParameter = parameters2.find(({ name }) => name === parameter.name);
10431
+ if (!matchingParameter) {
10432
+ return false;
10433
+ }
10434
+ // Note: Do not compare description, it is not relevant for compatibility
10435
+ if (matchingParameter.isInput !== parameter.isInput) {
10436
+ return false;
10437
+ }
10438
+ if (matchingParameter.isOutput !== parameter.isOutput) {
10439
+ return false;
10440
+ }
10441
+ }
10442
+ }
10443
+ return true;
10444
+ }
10445
+
10446
+ /**
10447
+ * Checks if a given pipeline satisfies the requirements of a specified pipeline interface.
10448
+ *
10449
+ * @deprecated https://github.com/webgptorg/promptbook/pull/186
10450
+ * @see https://github.com/webgptorg/promptbook/discussions/171
10451
+ * @returns `true` if the pipeline implements the interface, `false` otherwise.
10452
+ *
10453
+ * @public exported from `@promptbook/core`
10454
+ */
10455
+ function isPipelineImplementingInterface(options) {
10456
+ const { pipeline, pipelineInterface } = options;
10457
+ return isPipelineInterfacesEqual(getPipelineInterface(pipeline), pipelineInterface);
10458
+ }
10459
+
10460
+ /**
10225
10461
  * Set formfactor based on the pipeline interface e
10226
10462
  *
10227
10463
  * @private
@@ -10403,197 +10639,125 @@ const HIGH_LEVEL_ABSTRACTIONS = [
10403
10639
  // Note: [💞] Ignore a discrepancy between file name and entity name
10404
10640
 
10405
10641
  /**
10406
- * Supported script languages
10642
+ * Applies postprocessing and exports the parsed pipeline JSON.
10407
10643
  *
10408
- * @private internal base for `ScriptLanguage`
10644
+ * @private internal utility of `parsePipeline`
10409
10645
  */
10410
- const SUPPORTED_SCRIPT_LANGUAGES = ['javascript', 'typescript', 'python'];
10411
- // <- TODO: [🏥] DRY
10412
-
10646
+ function finalizeParsedPipeline($pipelineJson) {
10647
+ applyImplicitParameterDirections($pipelineJson);
10648
+ removeUndefinedValuesFromPipeline($pipelineJson);
10649
+ applySyncHighLevelAbstractions($pipelineJson);
10650
+ ensurePipelineFormfactor($pipelineJson);
10651
+ return exportParsedPipelineJson($pipelineJson);
10652
+ }
10413
10653
  /**
10414
- * Number of padding lines to add at the end of the book content
10654
+ * Applies default INPUT/OUTPUT flags when the author did not specify them explicitly.
10415
10655
  *
10416
- * @public exported from `@promptbook/core`
10656
+ * @private internal utility of `finalizeParsedPipeline`
10417
10657
  */
10418
- const PADDING_LINES = 11;
10658
+ function applyImplicitParameterDirections($pipelineJson) {
10659
+ markImplicitInputParameters($pipelineJson);
10660
+ markImplicitOutputParameters($pipelineJson);
10661
+ }
10419
10662
  /**
10420
- * A function that adds padding to the book content
10421
- *
10422
- * Note: [🔂] This function is idempotent.
10663
+ * Marks non-result parameters as pipeline inputs when no input was declared.
10423
10664
  *
10424
- * @public exported from `@promptbook/core`
10665
+ * @private internal utility of `finalizeParsedPipeline`
10425
10666
  */
10426
- function padBook(content) {
10427
- if (!content) {
10428
- return '\n'.repeat(PADDING_LINES);
10667
+ function markImplicitInputParameters($pipelineJson) {
10668
+ if ($pipelineJson.parameters.some((parameter) => parameter.isInput)) {
10669
+ return;
10429
10670
  }
10430
- const lines = content.split(/\r?\n/);
10431
- let trailingEmptyLines = 0;
10432
- for (let i = lines.length - 1; i >= 0; i--) {
10433
- const line = lines[i];
10434
- if (line === undefined) {
10435
- // Note: This should not happen in reality, but it's here to satisfy TypeScript's noUncheckedIndexedAccess option
10436
- continue;
10437
- }
10438
- if (line.trim() === '') {
10439
- trailingEmptyLines++;
10440
- }
10441
- else {
10442
- break;
10671
+ for (const parameter of $pipelineJson.parameters) {
10672
+ const isThisParameterResulting = $pipelineJson.tasks.some((task) => task.resultingParameterName === parameter.name);
10673
+ if (!isThisParameterResulting) {
10674
+ parameter.isInput = true;
10675
+ // <- TODO: [💔] Why this is making typescript error in vscode but not in cli
10676
+ // > Type 'true' is not assignable to type 'false'.ts(2322)
10677
+ // > (property) isInput: false
10678
+ // > The parameter is input of the pipeline The parameter is NOT input of the pipeline
10443
10679
  }
10444
10680
  }
10445
- if (trailingEmptyLines >= PADDING_LINES) {
10446
- return content;
10447
- }
10448
- const linesToAdd = PADDING_LINES - trailingEmptyLines;
10449
- return (content + '\n'.repeat(linesToAdd));
10450
10681
  }
10451
- // TODO: [🧠] Maybe export
10452
-
10453
10682
  /**
10454
- * Removes Markdown (or HTML) comments
10455
- *
10456
- * @param {string} content - The string to remove comments from.
10457
- * @returns {string} The input string with all comments removed.
10458
- *
10459
- * @public exported from `@promptbook/markdown-utils`
10460
- */
10461
- function removeMarkdownComments(content) {
10462
- return spaceTrim$1(content.replace(/<!--(.*?)-->/gs, ''));
10463
- }
10464
-
10465
- /**
10466
- * Utility to determine if a pipeline string is in flat format.
10467
- * A flat pipeline is a simple text without proper structure (headers, blocks, etc).
10468
- *
10469
- * @public exported from `@promptbook/editable`
10470
- */
10471
- function isFlatPipeline(pipelineString) {
10472
- pipelineString = removeMarkdownComments(pipelineString);
10473
- pipelineString = spaceTrim$1(pipelineString);
10474
- const isMarkdownBeginningWithHeadline = pipelineString.startsWith('# ');
10475
- //const isLastLineReturnStatement = pipelineString.split(/\r?\n/).pop()!.split('`').join('').startsWith('->');
10476
- const isBacktickBlockUsed = pipelineString.includes('```');
10477
- const isQuoteBlocksUsed = /^>\s+/m.test(pipelineString);
10478
- const isBlocksUsed = isBacktickBlockUsed || isQuoteBlocksUsed;
10479
- // TODO: [🧉] Also (double)check
10480
- // > const usedCommands
10481
- // > const isBlocksUsed
10482
- // > const returnStatementCount
10483
- const isFlat = !isMarkdownBeginningWithHeadline && !isBlocksUsed; /* && isLastLineReturnStatement */
10484
- return isFlat;
10485
- }
10486
-
10487
- /**
10488
- * Converts a pipeline structure to its string representation.
10489
- *
10490
- * Transforms a flat, simple pipeline into a properly formatted pipeline string
10491
- * with sections for title, prompt, and return statement.
10683
+ * Marks every non-input parameter as output when no output was declared.
10492
10684
  *
10493
- * @public exported from `@promptbook/editable`
10685
+ * @private internal utility of `finalizeParsedPipeline`
10494
10686
  */
10495
- function deflatePipeline(pipelineString) {
10496
- if (!isFlatPipeline(pipelineString)) {
10497
- return pipelineString;
10498
- }
10499
- pipelineString = spaceTrim$1(pipelineString);
10500
- const pipelineStringLines = pipelineString.split(/\r?\n/);
10501
- const potentialReturnStatement = pipelineStringLines.pop();
10502
- let returnStatement;
10503
- if (/(-|=)>\s*\{.*\}/.test(potentialReturnStatement)) {
10504
- // Note: Last line is return statement
10505
- returnStatement = potentialReturnStatement;
10506
- }
10507
- else {
10508
- // Note: Last line is not a return statement
10509
- returnStatement = `-> {${DEFAULT_BOOK_OUTPUT_PARAMETER_NAME}}`;
10510
- pipelineStringLines.push(potentialReturnStatement);
10511
- }
10512
- const prompt = spaceTrim$1(pipelineStringLines.join('\n'));
10513
- let quotedPrompt;
10514
- if (prompt.split(/\r?\n/).length <= 1) {
10515
- quotedPrompt = `> ${prompt}`;
10516
- }
10517
- else {
10518
- quotedPrompt = spaceTrim$1((block) => `
10519
- \`\`\`
10520
- ${block(prompt.split('`').join('\\`'))}
10521
- \`\`\`
10522
- `);
10687
+ function markImplicitOutputParameters($pipelineJson) {
10688
+ if ($pipelineJson.parameters.some((parameter) => parameter.isOutput)) {
10689
+ return;
10690
+ }
10691
+ for (const parameter of $pipelineJson.parameters) {
10692
+ if (!parameter.isInput) {
10693
+ parameter.isOutput = true;
10694
+ // <- TODO: [💔]
10695
+ }
10523
10696
  }
10524
- pipelineString = validatePipelineString(spaceTrim$1((block) => `
10525
- # ${DEFAULT_BOOK_TITLE}
10526
-
10527
- ## Prompt
10528
-
10529
- ${block(quotedPrompt)}
10530
-
10531
- ${returnStatement}
10532
- `));
10533
- // <- TODO: Maybe use book` notation
10534
- return padBook(pipelineString);
10535
10697
  }
10536
- // TODO: Unit test
10537
-
10538
10698
  /**
10539
- * Utility function to extract all list items from markdown
10540
- *
10541
- * Note: It works with both ul and ol
10542
- * Note: It omits list items in code blocks
10543
- * Note: It flattens nested lists
10544
- * Note: It can not work with html syntax and comments
10699
+ * Removes `undefined` properties from serialized tasks and parameters.
10545
10700
  *
10546
- * @param markdown any valid markdown
10547
- * @returns An array of strings, each representing an individual list item found in the markdown
10701
+ * @private internal utility of `finalizeParsedPipeline`
10702
+ */
10703
+ function removeUndefinedValuesFromPipeline($pipelineJson) {
10704
+ $pipelineJson.tasks.forEach(removeUndefinedProperties);
10705
+ $pipelineJson.parameters.forEach(removeUndefinedProperties);
10706
+ }
10707
+ /**
10708
+ * Deletes all own properties with `undefined` values from a mutable JSON entity.
10548
10709
  *
10549
- * @public exported from `@promptbook/markdown-utils`
10710
+ * @private internal utility of `finalizeParsedPipeline`
10550
10711
  */
10551
- function extractAllListItemsFromMarkdown(markdown) {
10552
- const lines = markdown.split(/\r?\n/);
10553
- const listItems = [];
10554
- let isInCodeBlock = false;
10555
- for (const line of lines) {
10556
- const trimmedLine = line.trim();
10557
- if (trimmedLine.startsWith('```')) {
10558
- isInCodeBlock = !isInCodeBlock;
10559
- }
10560
- if (!isInCodeBlock && (trimmedLine.startsWith('-') || trimmedLine.match(/^\d+\./))) {
10561
- const listItem = trimmedLine.replace(/^-|\d+\./, '').trim();
10562
- listItems.push(listItem);
10712
+ function removeUndefinedProperties(entity) {
10713
+ for (const [key, value] of Object.entries(entity)) {
10714
+ if (value === undefined) {
10715
+ delete entity[key];
10563
10716
  }
10564
10717
  }
10565
- return listItems;
10566
10718
  }
10567
-
10568
10719
  /**
10569
- * Extracts exactly ONE code block from markdown.
10570
- *
10571
- * - When there are multiple or no code blocks the function throws a `ParseError`
10572
- *
10573
- * Note: There are multiple similar functions:
10574
- * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
10575
- * - `extractJsonBlock` extracts exactly one valid JSON code block
10576
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
10577
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
10720
+ * Applies all sync-only high-level abstractions after parsing.
10578
10721
  *
10579
- * @param markdown any valid markdown
10580
- * @returns code block with language and content
10581
- * @throws {ParseError} if there is not exactly one code block in the markdown
10722
+ * @private internal utility of `finalizeParsedPipeline`
10723
+ */
10724
+ function applySyncHighLevelAbstractions($pipelineJson) {
10725
+ for (const highLevelAbstraction of HIGH_LEVEL_ABSTRACTIONS.filter(({ type }) => type === 'SYNC')) {
10726
+ highLevelAbstraction.$applyToPipelineJson($pipelineJson);
10727
+ }
10728
+ }
10729
+ /**
10730
+ * Ensures parsed pipelines always have the default `GENERIC` formfactor.
10582
10731
  *
10583
- * @public exported from `@promptbook/markdown-utils`
10732
+ * @private internal utility of `finalizeParsedPipeline`
10584
10733
  */
10585
- function extractOneBlockFromMarkdown(markdown) {
10586
- const codeBlocks = extractAllBlocksFromMarkdown(markdown);
10587
- if (codeBlocks.length !== 1) {
10588
- throw new ParseError(spaceTrim$1((block) => `
10589
- There should be exactly 1 code block in task section, found ${codeBlocks.length} code blocks
10590
-
10591
- ${block(codeBlocks.map((block, i) => `Block ${i + 1}:\n${block.content}`).join('\n\n\n'))}
10592
- `));
10734
+ function ensurePipelineFormfactor($pipelineJson) {
10735
+ // Note: [🔆] If formfactor is still not set, set it to 'GENERIC'
10736
+ if ($pipelineJson.formfactorName === undefined) {
10737
+ $pipelineJson.formfactorName = 'GENERIC';
10593
10738
  }
10594
- return codeBlocks[0];
10595
10739
  }
10596
- // TODO: [🍓][🌻] Decide of this is internal utility, external util OR validator/postprocessor
10740
+ /**
10741
+ * Finalizes ordering and exports the parsed pipeline JSON.
10742
+ *
10743
+ * @private internal utility of `finalizeParsedPipeline`
10744
+ */
10745
+ function exportParsedPipelineJson($pipelineJson) {
10746
+ return exportJson({
10747
+ name: 'pipelineJson',
10748
+ message: `Result of \`parsePipeline\``,
10749
+ order: ORDER_OF_PIPELINE_JSON,
10750
+ value: {
10751
+ formfactorName: 'GENERIC',
10752
+ // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
10753
+ ...$pipelineJson,
10754
+ },
10755
+ });
10756
+ }
10757
+ // TODO: Use spaceTrim more effectively
10758
+ // TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
10759
+ // TODO: [♈] Probably move expectations from tasks to parameters
10760
+ // TODO: [🍙] Make some standard order of json properties
10597
10761
 
10598
10762
  /**
10599
10763
  * Parses markdown section to title its level and content
@@ -10665,213 +10829,24 @@ function splitMarkdownIntoSections(markdown) {
10665
10829
  currentType = 'MARKDOWN';
10666
10830
  }
10667
10831
  }
10668
- else if (currentType === 'COMMENT') {
10669
- buffer.push(line);
10670
- if (line.includes('-->')) {
10671
- currentType = 'MARKDOWN';
10672
- }
10673
- }
10674
- }
10675
- finishSection();
10676
- return sections;
10677
- }
10678
- /**
10679
- * TODO: [🏛] This can be part of markdown builder
10680
- * Note: [🕞] In past (commit 42086e1603cbed506482997c00a8ee979af0a247) there was much more
10681
- * sophisticated implementation of this function through parsing markdown into JSON structure
10682
- * and flattening the actual structure
10683
- * NOW we are working just with markdown string and its good enough
10684
- */
10685
-
10686
- /**
10687
- * Normalizes the markdown by flattening the structure
10688
- *
10689
- * - It always have h1 - if there is no h1 in the markdown, it will be added `DEFAULT_BOOK_TITLE`
10690
- * - All other headings are normalized to h2
10691
- *
10692
- * @public exported from `@promptbook/markdown-utils`
10693
- */
10694
- function flattenMarkdown(markdown) {
10695
- const sections = splitMarkdownIntoSections(markdown);
10696
- if (sections.length === 0) {
10697
- return `# ${DEFAULT_BOOK_TITLE}`;
10698
- }
10699
- let flattenedMarkdown = '';
10700
- const parsedSections = sections.map(parseMarkdownSection);
10701
- const firstSection = parsedSections.shift();
10702
- if (firstSection.level === 1) {
10703
- flattenedMarkdown += `# ${firstSection.title}` + `\n\n`;
10704
- flattenedMarkdown += firstSection.content + `\n\n`; // <- [🧠] Maybe 3 new lines?
10705
- }
10706
- else {
10707
- parsedSections.unshift(firstSection);
10708
- flattenedMarkdown += `# ${DEFAULT_BOOK_TITLE}` + `\n\n`; // <- [🧠] Maybe 3 new lines?
10709
- }
10710
- for (const { title, content } of parsedSections) {
10711
- flattenedMarkdown += `## ${title}` + `\n\n`;
10712
- flattenedMarkdown += content + `\n\n`; // <- [🧠] Maybe 3 new lines?
10713
- }
10714
- return spaceTrim$1(flattenedMarkdown);
10715
- }
10716
- /**
10717
- * TODO: [🏛] This can be part of markdown builder
10718
- * Note: [🕞] In past (commit 42086e1603cbed506482997c00a8ee979af0a247) there was much more
10719
- * sophisticated implementation of this function through parsing markdown into JSON structure
10720
- * and flattening the actual structure
10721
- * NOW we are working just with markdown string and its good enough
10722
- */
10723
-
10724
- /**
10725
- * Normalizes inline parameter mentions wrapped in code spans before markdown flattening.
10726
- *
10727
- * @private internal utility of `parsePipeline`
10728
- */
10729
- const INLINE_CODE_PARAMETER_REGEXP = /`\{(?<parameterName>[a-z0-9_]+)\}`/gi;
10730
- /**
10731
- * Normalizes inline return statements wrapped in code spans before markdown flattening.
10732
- *
10733
- * @private internal utility of `parsePipeline`
10734
- */
10735
- const INLINE_CODE_RETURN_PARAMETER_REGEXP = /`->\s+\{(?<parameterName>[a-z0-9_]+)\}`/gi;
10736
- /**
10737
- * Matches the trailing return statement of a task section.
10738
- *
10739
- * @private internal utility of `parsePipeline`
10740
- */
10741
- const RESULTING_PARAMETER_LINE_REGEXP = /^->\s*\{(?<resultingParamName>[a-z0-9_]+)\}/im;
10742
- /**
10743
- * Removes fenced code blocks when deriving human-readable section descriptions.
10744
- *
10745
- * @private internal utility of `parsePipeline`
10746
- */
10747
- const DESCRIPTION_CODE_BLOCK_REGEXP = /^```.*^```/gms;
10748
- /**
10749
- * Removes blockquote lines when deriving human-readable section descriptions.
10750
- *
10751
- * @private internal utility of `parsePipeline`
10752
- */
10753
- const DESCRIPTION_BLOCKQUOTE_REGEXP = /^>.*$/gm;
10754
- /**
10755
- * Removes list items and return statements when deriving human-readable section descriptions.
10756
- *
10757
- * @private internal utility of `parsePipeline`
10758
- */
10759
- const DESCRIPTION_LIST_ITEM_REGEXP = /^(?:(?:-)|(?:\d\))|(?:`?->))\s+.*$/gm;
10760
- /**
10761
- * Compile pipeline from string (markdown) format to JSON format synchronously
10762
- *
10763
- * Note: There are 3 similar functions:
10764
- * - `compilePipeline` **(preferred)** - which properly compiles the promptbook and uses embedding for external knowledge
10765
- * - `parsePipeline` - use only if you need to compile promptbook synchronously and it contains NO external knowledge
10766
- * - `preparePipeline` - just one step in the compilation process
10767
- *
10768
- * Note: This function does not validate logic of the pipeline only the parsing
10769
- * Note: This function acts as compilation process
10770
- *
10771
- * @param pipelineString {Promptbook} in string markdown format (.book.md)
10772
- * @returns {Promptbook} compiled in JSON format (.bookc)
10773
- * @throws {ParseError} if the promptbook string is not valid
10774
- *
10775
- * @public exported from `@promptbook/core`
10776
- */
10777
- function parsePipeline(pipelineString) {
10778
- const $pipelineJson = createInitialPipelineJson(pipelineString);
10779
- const preparedPipelineString = preparePipelineString(pipelineString, $pipelineJson);
10780
- const { pipelineHead, pipelineSections } = parsePreparedPipelineSections(preparedPipelineString, $pipelineJson);
10781
- const getUniqueSectionName = createUniqueSectionNameResolver(pipelineSections);
10782
- applyPipelineHead(pipelineHead, $pipelineJson);
10783
- for (const pipelineSection of pipelineSections) {
10784
- processPipelineSection(pipelineSection, $pipelineJson, getUniqueSectionName);
10785
- }
10786
- applyImplicitParameterDirections($pipelineJson);
10787
- removeUndefinedValuesFromPipeline($pipelineJson);
10788
- applySyncHighLevelAbstractions($pipelineJson);
10789
- ensurePipelineFormfactor($pipelineJson);
10790
- return exportParsedPipelineJson($pipelineJson);
10791
- }
10792
- /**
10793
- * Creates the mutable pipeline JSON structure used throughout parsing.
10794
- *
10795
- * @private internal utility of `parsePipeline`
10796
- */
10797
- function createInitialPipelineJson(pipelineString) {
10798
- return {
10799
- title: DEFAULT_BOOK_TITLE,
10800
- parameters: [],
10801
- tasks: [],
10802
- knowledgeSources: [],
10803
- knowledgePieces: [],
10804
- personas: [],
10805
- preparations: [],
10806
- sources: [
10807
- {
10808
- type: 'BOOK',
10809
- path: null,
10810
- // <- TODO: !!6 Pass here path of the file
10811
- content: pipelineString,
10812
- },
10813
- ],
10814
- };
10815
- }
10816
- /**
10817
- * Builds a short file/url identification block for parse errors.
10818
- *
10819
- * @private internal utility of `parsePipeline`
10820
- */
10821
- function getPipelineIdentification($pipelineJson) {
10822
- // Note: This is a 😐 implementation of [🚞]
10823
- const pipelineIdentificationParts = [];
10824
- if ($pipelineJson.sourceFile !== undefined) {
10825
- pipelineIdentificationParts.push(`File: ${$pipelineJson.sourceFile}`);
10826
- }
10827
- if ($pipelineJson.pipelineUrl !== undefined) {
10828
- pipelineIdentificationParts.push(`Url: ${$pipelineJson.pipelineUrl}`);
10832
+ else if (currentType === 'COMMENT') {
10833
+ buffer.push(line);
10834
+ if (line.includes('-->')) {
10835
+ currentType = 'MARKDOWN';
10836
+ }
10837
+ }
10829
10838
  }
10830
- return pipelineIdentificationParts.join('\n');
10831
- }
10832
- /**
10833
- * Removes shebang/comments and normalizes markdown into a parseable pipeline form.
10834
- *
10835
- * @private internal utility of `parsePipeline`
10836
- */
10837
- function preparePipelineString(pipelineString, $pipelineJson) {
10838
- pipelineString = removePipelineShebang(pipelineString, $pipelineJson);
10839
- pipelineString = removeMarkdownComments(pipelineString);
10840
- pipelineString = spaceTrim$1(pipelineString);
10841
- // <- TODO: [😧] `spaceTrim` should preserve discriminated type *(or at lease `PipelineString`)*
10842
- pipelineString = deflatePipeline(pipelineString);
10843
- pipelineString = flattenMarkdown(pipelineString);
10844
- pipelineString = pipelineString.replaceAll(INLINE_CODE_PARAMETER_REGEXP, '{$<parameterName>}');
10845
- pipelineString = pipelineString.replaceAll(INLINE_CODE_RETURN_PARAMETER_REGEXP, '-> {$<parameterName>}');
10846
- return pipelineString;
10839
+ finishSection();
10840
+ return sections;
10847
10841
  }
10848
10842
  /**
10849
- * Validates and removes the optional `#!` shebang line for `.book` files.
10850
- *
10851
- * @private internal utility of `parsePipeline`
10843
+ * TODO: [🏛] This can be part of markdown builder
10844
+ * Note: [🕞] In past (commit 42086e1603cbed506482997c00a8ee979af0a247) there was much more
10845
+ * sophisticated implementation of this function through parsing markdown into JSON structure
10846
+ * and flattening the actual structure
10847
+ * NOW we are working just with markdown string and its good enough
10852
10848
  */
10853
- function removePipelineShebang(pipelineString, $pipelineJson) {
10854
- if (!pipelineString.startsWith('#!')) {
10855
- return pipelineString;
10856
- }
10857
- const [shebangLine, ...restLines] = pipelineString.split(/\r?\n/);
10858
- const isBookShebang = (shebangLine || '').includes('ptbk');
10859
- if (!isBookShebang) {
10860
- throw new ParseError(spaceTrim$1((block) => `
10861
- It seems that you try to parse a book file which has non-standard shebang line for book files:
10862
- Shebang line must contain 'ptbk'
10863
-
10864
- You have:
10865
- ${block(shebangLine || '(empty line)')}
10866
-
10867
- It should look like this:
10868
- #!/usr/bin/env ptbk
10869
10849
 
10870
- ${block(getPipelineIdentification($pipelineJson))}
10871
- `));
10872
- }
10873
- return validatePipelineString(restLines.join('\n'));
10874
- }
10875
10850
  /**
10876
10851
  * Splits the prepared markdown into the pipeline head and task sections.
10877
10852
  *
@@ -10888,7 +10863,7 @@ function parsePreparedPipelineSections(pipelineString, $pipelineJson) {
10888
10863
  /**
10889
10864
  * Ensures the flattened markdown has exactly one h1 head followed by only h2 sections.
10890
10865
  *
10891
- * @private internal utility of `parsePipeline`
10866
+ * @private internal utility of `parsePreparedPipelineSections`
10892
10867
  */
10893
10868
  function assertPipelineSectionsStructure(pipelineHead, pipelineSections, $pipelineJson) {
10894
10869
  if (pipelineHead === undefined) {
@@ -10919,148 +10894,270 @@ function assertPipelineSectionsStructure(pipelineHead, pipelineSections, $pipeli
10919
10894
  `));
10920
10895
  }
10921
10896
  }
10897
+
10922
10898
  /**
10923
- * Applies the pipeline head title, description, and head-level commands.
10899
+ * Number of padding lines to add at the end of the book content
10924
10900
  *
10925
- * @private internal utility of `parsePipeline`
10901
+ * @public exported from `@promptbook/core`
10926
10902
  */
10927
- function applyPipelineHead(pipelineHead, $pipelineJson) {
10928
- $pipelineJson.title = pipelineHead.title;
10929
- $pipelineJson.description = extractPipelineDescription(pipelineHead.content);
10930
- for (const listItem of extractAllListItemsFromMarkdown(pipelineHead.content)) {
10931
- applyPipelineHeadCommand(listItem, $pipelineJson);
10903
+ const PADDING_LINES = 11;
10904
+ /**
10905
+ * A function that adds padding to the book content
10906
+ *
10907
+ * Note: [🔂] This function is idempotent.
10908
+ *
10909
+ * @public exported from `@promptbook/core`
10910
+ */
10911
+ function padBook(content) {
10912
+ if (!content) {
10913
+ return '\n'.repeat(PADDING_LINES);
10914
+ }
10915
+ const lines = content.split(/\r?\n/);
10916
+ let trailingEmptyLines = 0;
10917
+ for (let i = lines.length - 1; i >= 0; i--) {
10918
+ const line = lines[i];
10919
+ if (line === undefined) {
10920
+ // Note: This should not happen in reality, but it's here to satisfy TypeScript's noUncheckedIndexedAccess option
10921
+ continue;
10922
+ }
10923
+ if (line.trim() === '') {
10924
+ trailingEmptyLines++;
10925
+ }
10926
+ else {
10927
+ break;
10928
+ }
10929
+ }
10930
+ if (trailingEmptyLines >= PADDING_LINES) {
10931
+ return content;
10932
10932
  }
10933
+ const linesToAdd = PADDING_LINES - trailingEmptyLines;
10934
+ return (content + '\n'.repeat(linesToAdd));
10933
10935
  }
10936
+ // TODO: [🧠] Maybe export
10937
+
10934
10938
  /**
10935
- * Extracts the plain-text description from a head or task section body.
10939
+ * Removes Markdown (or HTML) comments
10936
10940
  *
10937
- * @private internal utility of `parsePipeline`
10941
+ * @param {string} content - The string to remove comments from.
10942
+ * @returns {string} The input string with all comments removed.
10943
+ *
10944
+ * @public exported from `@promptbook/markdown-utils`
10938
10945
  */
10939
- function extractPipelineDescription(sectionContent) {
10940
- let description = sectionContent;
10941
- description = description.split(DESCRIPTION_CODE_BLOCK_REGEXP).join('');
10942
- description = description.split(DESCRIPTION_BLOCKQUOTE_REGEXP).join('');
10943
- description = description.split(DESCRIPTION_LIST_ITEM_REGEXP).join('');
10944
- description = spaceTrim$1(description);
10945
- if (description === '') {
10946
- return undefined;
10947
- }
10948
- return description;
10946
+ function removeMarkdownComments(content) {
10947
+ return spaceTrim$1(content.replace(/<!--(.*?)-->/gs, ''));
10949
10948
  }
10949
+
10950
10950
  /**
10951
- * Parses and applies one command declared in the pipeline head.
10951
+ * Utility to determine if a pipeline string is in flat format.
10952
+ * A flat pipeline is a simple text without proper structure (headers, blocks, etc).
10952
10953
  *
10953
- * @private internal utility of `parsePipeline`
10954
+ * @public exported from `@promptbook/editable`
10954
10955
  */
10955
- function applyPipelineHeadCommand(listItem, $pipelineJson) {
10956
- const command = parseCommand(listItem, 'PIPELINE_HEAD');
10957
- const commandParser = getParserForCommand(command);
10958
- if (commandParser.isUsedInPipelineHead !== true /* <- Note: [🦦][4] */) {
10959
- throw new ParseError(spaceTrim$1((block) => `
10960
- Command \`${command.type}\` is not allowed in the head of the pipeline ONLY at the pipeline task
10956
+ function isFlatPipeline(pipelineString) {
10957
+ pipelineString = removeMarkdownComments(pipelineString);
10958
+ pipelineString = spaceTrim$1(pipelineString);
10959
+ const isMarkdownBeginningWithHeadline = pipelineString.startsWith('# ');
10960
+ //const isLastLineReturnStatement = pipelineString.split(/\r?\n/).pop()!.split('`').join('').startsWith('->');
10961
+ const isBacktickBlockUsed = pipelineString.includes('```');
10962
+ const isQuoteBlocksUsed = /^>\s+/m.test(pipelineString);
10963
+ const isBlocksUsed = isBacktickBlockUsed || isQuoteBlocksUsed;
10964
+ // TODO: [🧉] Also (double)check
10965
+ // > const usedCommands
10966
+ // > const isBlocksUsed
10967
+ // > const returnStatementCount
10968
+ const isFlat = !isMarkdownBeginningWithHeadline && !isBlocksUsed; /* && isLastLineReturnStatement */
10969
+ return isFlat;
10970
+ }
10961
10971
 
10962
- ${block(getPipelineIdentification($pipelineJson))}
10963
- `)); // <- TODO: [🚞]
10972
+ /**
10973
+ * Converts a pipeline structure to its string representation.
10974
+ *
10975
+ * Transforms a flat, simple pipeline into a properly formatted pipeline string
10976
+ * with sections for title, prompt, and return statement.
10977
+ *
10978
+ * @public exported from `@promptbook/editable`
10979
+ */
10980
+ function deflatePipeline(pipelineString) {
10981
+ if (!isFlatPipeline(pipelineString)) {
10982
+ return pipelineString;
10964
10983
  }
10965
- try {
10966
- commandParser.$applyToPipelineJson(command, $pipelineJson);
10967
- // <- Note: [🦦] Its strange that this assertion must be here, [🦦][4] should do this assertion implicitly
10984
+ pipelineString = spaceTrim$1(pipelineString);
10985
+ const pipelineStringLines = pipelineString.split(/\r?\n/);
10986
+ const potentialReturnStatement = pipelineStringLines.pop();
10987
+ let returnStatement;
10988
+ if (/(-|=)>\s*\{.*\}/.test(potentialReturnStatement)) {
10989
+ // Note: Last line is return statement
10990
+ returnStatement = potentialReturnStatement;
10991
+ }
10992
+ else {
10993
+ // Note: Last line is not a return statement
10994
+ returnStatement = `-> {${DEFAULT_BOOK_OUTPUT_PARAMETER_NAME}}`;
10995
+ pipelineStringLines.push(potentialReturnStatement);
10996
+ }
10997
+ const prompt = spaceTrim$1(pipelineStringLines.join('\n'));
10998
+ let quotedPrompt;
10999
+ if (prompt.split(/\r?\n/).length <= 1) {
11000
+ quotedPrompt = `> ${prompt}`;
11001
+ }
11002
+ else {
11003
+ quotedPrompt = spaceTrim$1((block) => `
11004
+ \`\`\`
11005
+ ${block(prompt.split('`').join('\\`'))}
11006
+ \`\`\`
11007
+ `);
11008
+ }
11009
+ pipelineString = validatePipelineString(spaceTrim$1((block) => `
11010
+ # ${DEFAULT_BOOK_TITLE}
11011
+
11012
+ ## Prompt
11013
+
11014
+ ${block(quotedPrompt)}
11015
+
11016
+ ${returnStatement}
11017
+ `));
11018
+ // <- TODO: Maybe use book` notation
11019
+ return padBook(pipelineString);
11020
+ }
11021
+ // TODO: Unit test
11022
+
11023
+ /**
11024
+ * Normalizes the markdown by flattening the structure
11025
+ *
11026
+ * - It always have h1 - if there is no h1 in the markdown, it will be added `DEFAULT_BOOK_TITLE`
11027
+ * - All other headings are normalized to h2
11028
+ *
11029
+ * @public exported from `@promptbook/markdown-utils`
11030
+ */
11031
+ function flattenMarkdown(markdown) {
11032
+ const sections = splitMarkdownIntoSections(markdown);
11033
+ if (sections.length === 0) {
11034
+ return `# ${DEFAULT_BOOK_TITLE}`;
11035
+ }
11036
+ let flattenedMarkdown = '';
11037
+ const parsedSections = sections.map(parseMarkdownSection);
11038
+ const firstSection = parsedSections.shift();
11039
+ if (firstSection.level === 1) {
11040
+ flattenedMarkdown += `# ${firstSection.title}` + `\n\n`;
11041
+ flattenedMarkdown += firstSection.content + `\n\n`; // <- [🧠] Maybe 3 new lines?
11042
+ }
11043
+ else {
11044
+ parsedSections.unshift(firstSection);
11045
+ flattenedMarkdown += `# ${DEFAULT_BOOK_TITLE}` + `\n\n`; // <- [🧠] Maybe 3 new lines?
11046
+ }
11047
+ for (const { title, content } of parsedSections) {
11048
+ flattenedMarkdown += `## ${title}` + `\n\n`;
11049
+ flattenedMarkdown += content + `\n\n`; // <- [🧠] Maybe 3 new lines?
11050
+ }
11051
+ return spaceTrim$1(flattenedMarkdown);
11052
+ }
11053
+ /**
11054
+ * TODO: [🏛] This can be part of markdown builder
11055
+ * Note: [🕞] In past (commit 42086e1603cbed506482997c00a8ee979af0a247) there was much more
11056
+ * sophisticated implementation of this function through parsing markdown into JSON structure
11057
+ * and flattening the actual structure
11058
+ * NOW we are working just with markdown string and its good enough
11059
+ */
11060
+
11061
+ /**
11062
+ * Normalizes inline parameter mentions wrapped in code spans before markdown flattening.
11063
+ *
11064
+ * @private internal utility of `preparePipelineString`
11065
+ */
11066
+ const INLINE_CODE_PARAMETER_REGEXP = /`\{(?<parameterName>[a-z0-9_]+)\}`/gi;
11067
+ /**
11068
+ * Normalizes inline return statements wrapped in code spans before markdown flattening.
11069
+ *
11070
+ * @private internal utility of `preparePipelineString`
11071
+ */
11072
+ const INLINE_CODE_RETURN_PARAMETER_REGEXP = /`->\s+\{(?<parameterName>[a-z0-9_]+)\}`/gi;
11073
+ /**
11074
+ * Removes shebang/comments and normalizes markdown into a parseable pipeline form.
11075
+ *
11076
+ * @private internal utility of `parsePipeline`
11077
+ */
11078
+ function preparePipelineString(pipelineString, $pipelineJson) {
11079
+ pipelineString = removePipelineShebang(pipelineString, $pipelineJson);
11080
+ pipelineString = removeMarkdownComments(pipelineString);
11081
+ pipelineString = spaceTrim$1(pipelineString);
11082
+ // <- TODO: [😧] `spaceTrim` should preserve discriminated type *(or at lease `PipelineString`)*
11083
+ pipelineString = deflatePipeline(pipelineString);
11084
+ pipelineString = flattenMarkdown(pipelineString);
11085
+ pipelineString = pipelineString.replaceAll(INLINE_CODE_PARAMETER_REGEXP, '{$<parameterName>}');
11086
+ pipelineString = pipelineString.replaceAll(INLINE_CODE_RETURN_PARAMETER_REGEXP, '-> {$<parameterName>}');
11087
+ return pipelineString;
11088
+ }
11089
+ /**
11090
+ * Validates and removes the optional `#!` shebang line for `.book` files.
11091
+ *
11092
+ * @private internal utility of `preparePipelineString`
11093
+ */
11094
+ function removePipelineShebang(pipelineString, $pipelineJson) {
11095
+ if (!pipelineString.startsWith('#!')) {
11096
+ return pipelineString;
10968
11097
  }
10969
- catch (error) {
10970
- if (!(error instanceof ParseError)) {
10971
- throw error;
10972
- }
11098
+ const [shebangLine, ...restLines] = pipelineString.split(/\r?\n/);
11099
+ const isBookShebang = (shebangLine || '').includes('ptbk');
11100
+ if (!isBookShebang) {
10973
11101
  throw new ParseError(spaceTrim$1((block) => `
10974
- Command ${command.type} failed to apply to the pipeline
10975
-
10976
- The error:
10977
- ${block(error.message)}
11102
+ It seems that you try to parse a book file which has non-standard shebang line for book files:
11103
+ Shebang line must contain 'ptbk'
10978
11104
 
10979
- Raw command:
10980
- - ${listItem}
11105
+ You have:
11106
+ ${block(shebangLine || '(empty line)')}
10981
11107
 
10982
- Usage of ${command.type}:
10983
- ${block(commandParser.examples.map((example) => `- ${example}`).join('\n'))}
11108
+ It should look like this:
11109
+ #!/usr/bin/env ptbk
10984
11110
 
10985
11111
  ${block(getPipelineIdentification($pipelineJson))}
10986
- `)); // <- TODO: [🚞]
10987
- }
10988
- if (command.type === 'PARAMETER') {
10989
- defineParameter($pipelineJson, command);
10990
- // <- Note: [🍣]
11112
+ `));
10991
11113
  }
11114
+ return validatePipelineString(restLines.join('\n'));
10992
11115
  }
11116
+
10993
11117
  /**
10994
- * Merges one parameter declaration into the mutable pipeline parameter list.
11118
+ * Supported script languages
10995
11119
  *
10996
- * @private internal utility of `parsePipeline`
11120
+ * @private internal base for `ScriptLanguage`
10997
11121
  */
10998
- function defineParameter($pipelineJson, parameterCommand) {
10999
- const { parameterName, parameterDescription, isInput, isOutput } = parameterCommand;
11000
- if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
11001
- throw new ParseError(spaceTrim$1((block) => `
11002
- Parameter name {${parameterName}} is reserved and cannot be used as resulting parameter name
11122
+ const SUPPORTED_SCRIPT_LANGUAGES = ['javascript', 'typescript', 'python'];
11123
+ // <- TODO: [🏥] DRY
11003
11124
 
11004
- ${block(getPipelineIdentification($pipelineJson))}
11005
- `) /* <- TODO: [🚞] */);
11006
- }
11007
- const existingParameter = $pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
11008
- if (existingParameter &&
11009
- existingParameter.description &&
11010
- existingParameter.description !== parameterDescription &&
11011
- parameterDescription) {
11125
+ /**
11126
+ * Extracts exactly ONE code block from markdown.
11127
+ *
11128
+ * - When there are multiple or no code blocks the function throws a `ParseError`
11129
+ *
11130
+ * Note: There are multiple similar functions:
11131
+ * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
11132
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
11133
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
11134
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
11135
+ *
11136
+ * @param markdown any valid markdown
11137
+ * @returns code block with language and content
11138
+ * @throws {ParseError} if there is not exactly one code block in the markdown
11139
+ *
11140
+ * @public exported from `@promptbook/markdown-utils`
11141
+ */
11142
+ function extractOneBlockFromMarkdown(markdown) {
11143
+ const codeBlocks = extractAllBlocksFromMarkdown(markdown);
11144
+ if (codeBlocks.length !== 1) {
11012
11145
  throw new ParseError(spaceTrim$1((block) => `
11013
- Parameter \`{${parameterName}}\` is defined multiple times with different description:
11014
-
11015
- ${block(getPipelineIdentification($pipelineJson))}
11016
-
11017
- First definition:
11018
- ${block(existingParameter.description || '[undefined]')}
11146
+ There should be exactly 1 code block in task section, found ${codeBlocks.length} code blocks
11019
11147
 
11020
- Second definition:
11021
- ${block(parameterDescription || '[undefined]')}
11148
+ ${block(codeBlocks.map((block, i) => `Block ${i + 1}:\n${block.content}`).join('\n\n\n'))}
11022
11149
  `));
11023
11150
  }
11024
- if (existingParameter) {
11025
- if (parameterDescription) {
11026
- existingParameter.description = parameterDescription;
11027
- }
11028
- existingParameter.isInput = existingParameter.isInput || isInput;
11029
- existingParameter.isOutput = existingParameter.isOutput || isOutput;
11030
- return;
11031
- }
11032
- $pipelineJson.parameters.push({
11033
- name: parameterName,
11034
- description: parameterDescription || undefined,
11035
- isInput,
11036
- isOutput,
11037
- });
11151
+ return codeBlocks[0];
11038
11152
  }
11153
+ // TODO: [🍓][🌻] Decide of this is internal utility, external util OR validator/postprocessor
11154
+
11039
11155
  /**
11040
- * Creates stable unique task names for duplicate section titles.
11156
+ * Matches the trailing return statement of a task section.
11041
11157
  *
11042
- * @private internal utility of `parsePipeline`
11158
+ * @private internal utility of `processPipelineSection`
11043
11159
  */
11044
- function createUniqueSectionNameResolver(pipelineSections) {
11045
- const sectionCounts = {};
11046
- for (const pipelineSection of pipelineSections) {
11047
- const sectionName = titleToName(pipelineSection.title);
11048
- if (sectionCounts[sectionName] === undefined) {
11049
- sectionCounts[sectionName] = { count: 0, currentIndex: 0 };
11050
- }
11051
- sectionCounts[sectionName].count++;
11052
- }
11053
- return (title) => {
11054
- const sectionName = titleToName(title);
11055
- const sectionCount = sectionCounts[sectionName];
11056
- if (sectionCount.count === 1) {
11057
- return sectionName;
11058
- }
11059
- const nameWithSuffix = `${sectionName}-${sectionCount.currentIndex}`;
11060
- sectionCount.currentIndex++;
11061
- return nameWithSuffix;
11062
- };
11063
- }
11160
+ const RESULTING_PARAMETER_LINE_REGEXP = /^->\s*\{(?<resultingParamName>[a-z0-9_]+)\}/im;
11064
11161
  /**
11065
11162
  * Parses, applies, and persists one h2 task section.
11066
11163
  *
@@ -11080,7 +11177,7 @@ function processPipelineSection(pipelineSection, $pipelineJson, getUniqueSection
11080
11177
  /**
11081
11178
  * Creates the mutable task JSON shell from one markdown section.
11082
11179
  *
11083
- * @private internal utility of `parsePipeline`
11180
+ * @private internal utility of `processPipelineSection`
11084
11181
  */
11085
11182
  function createTaskJsonFromSection(pipelineSection, getUniqueSectionName) {
11086
11183
  const { language, content } = extractOneBlockFromMarkdown(pipelineSection.content);
@@ -11108,7 +11205,7 @@ function createTaskJsonFromSection(pipelineSection, getUniqueSectionName) {
11108
11205
  /**
11109
11206
  * Extracts the optional trailing `-> {parameter}` statement from a section body.
11110
11207
  *
11111
- * @private internal utility of `parsePipeline`
11208
+ * @private internal utility of `processPipelineSection`
11112
11209
  */
11113
11210
  function extractResultingParameterName(sectionContent) {
11114
11211
  var _a;
@@ -11119,7 +11216,7 @@ function extractResultingParameterName(sectionContent) {
11119
11216
  /**
11120
11217
  * Parses all list-item commands declared inside one task section.
11121
11218
  *
11122
- * @private internal utility of `parsePipeline`
11219
+ * @private internal utility of `processPipelineSection`
11123
11220
  */
11124
11221
  function parsePipelineTaskCommands(sectionContent) {
11125
11222
  return extractAllListItemsFromMarkdown(sectionContent).map((listItem) => ({
@@ -11130,7 +11227,7 @@ function parsePipelineTaskCommands(sectionContent) {
11130
11227
  /**
11131
11228
  * Applies the implicit default `PROMPT_TASK` section type when no SECTION command is present.
11132
11229
  *
11133
- * @private internal utility of `parsePipeline`
11230
+ * @private internal utility of `processPipelineSection`
11134
11231
  */
11135
11232
  function applyDefaultTaskSectionType($taskJson, commands, $pipelineJson) {
11136
11233
  const isSectionCommandPresent = commands.some(({ command }) => command.type === 'SECTION');
@@ -11142,7 +11239,7 @@ function applyDefaultTaskSectionType($taskJson, commands, $pipelineJson) {
11142
11239
  /**
11143
11240
  * Parses and applies one command declared inside a task section.
11144
11241
  *
11145
- * @private internal utility of `parsePipeline`
11242
+ * @private internal utility of `processPipelineSection`
11146
11243
  */
11147
11244
  function applyPipelineTaskCommand(commandItem, $taskJson, $pipelineJson) {
11148
11245
  const { listItem, command } = commandItem;
@@ -11193,7 +11290,7 @@ function applyPipelineTaskCommand(commandItem, $taskJson, $pipelineJson) {
11193
11290
  /**
11194
11291
  * Validates and stores the language for SCRIPT tasks.
11195
11292
  *
11196
- * @private internal utility of `parsePipeline`
11293
+ * @private internal utility of `processPipelineSection`
11197
11294
  */
11198
11295
  function applyScriptTaskLanguage($taskJson, language, $pipelineJson) {
11199
11296
  const isScriptTask = $taskJson.taskType === 'SCRIPT_TASK';
@@ -11210,7 +11307,6 @@ function applyScriptTaskLanguage($taskJson, language, $pipelineJson) {
11210
11307
  if (!SUPPORTED_SCRIPT_LANGUAGES.includes(language)) {
11211
11308
  throw new ParseError(spaceTrim$1((block) => `
11212
11309
  Script language ${language} is not supported.
11213
-
11214
11310
  Supported languages are:
11215
11311
  ${block(SUPPORTED_SCRIPT_LANGUAGES.join(', '))}
11216
11312
 
@@ -11219,168 +11315,87 @@ function applyScriptTaskLanguage($taskJson, language, $pipelineJson) {
11219
11315
  $taskJson.contentLanguage = language;
11220
11316
  }
11221
11317
  /**
11222
- * Extracts task dependencies and ensures referenced parameters exist.
11223
- *
11224
- * @private internal utility of `parsePipeline`
11225
- */
11226
- function registerTaskDependentParameters($taskJson, $pipelineJson) {
11227
- $taskJson.dependentParameterNames = Array.from(extractParameterNamesFromTask($taskJson));
11228
- for (const parameterName of $taskJson.dependentParameterNames) {
11229
- // TODO: [🧠] This definition should be made first in the task
11230
- defineParameter($pipelineJson, {
11231
- parameterName,
11232
- parameterDescription: null,
11233
- isInput: false,
11234
- isOutput: false,
11235
- // <- Note: In this case null+false+false means that we do not know yet if it is input or output and we will set it later
11236
- });
11237
- }
11238
- }
11239
- /**
11240
- * Removes transient parsing flags and persists real tasks into the pipeline JSON.
11241
- *
11242
- * @private internal utility of `parsePipeline`
11243
- */
11244
- function persistTaskIfNeeded($taskJson, $pipelineJson) {
11245
- /*
11246
- // TODO: [🍧] This should be checked in `MODEL` command + better error message
11247
- if ($taskJson.taskType !== 'PROMPT_TASK' && $taskJson.modelRequirements !== undefined) {
11248
- throw new UnexpectedError(
11249
- spaceTrim(
11250
- (block) => `
11251
- Model requirements are defined for the block type ${
11252
- $taskJson.taskType
11253
- } which is not a \`PROMPT\` task
11254
-
11255
- This should be avoided by the \`modelCommandParser\`
11256
-
11257
- ${block(getPipelineIdentification($pipelineJson))}
11258
- `,
11259
- ),
11260
- );
11261
- }
11262
- */
11263
- if (!$taskJson.isTask) {
11264
- return;
11265
- }
11266
- delete $taskJson.isSectionTypeSet;
11267
- delete $taskJson.isTask;
11268
- // TODO: [🍙] Maybe do reorder of `$taskJson` here
11269
- $pipelineJson.tasks.push($taskJson);
11270
- }
11271
- /**
11272
- * Applies default INPUT/OUTPUT flags when the author did not specify them explicitly.
11273
- *
11274
- * @private internal utility of `parsePipeline`
11275
- */
11276
- function applyImplicitParameterDirections($pipelineJson) {
11277
- markImplicitInputParameters($pipelineJson);
11278
- markImplicitOutputParameters($pipelineJson);
11279
- }
11280
- /**
11281
- * Marks non-result parameters as pipeline inputs when no input was declared.
11282
- *
11283
- * @private internal utility of `parsePipeline`
11284
- */
11285
- function markImplicitInputParameters($pipelineJson) {
11286
- if ($pipelineJson.parameters.some((parameter) => parameter.isInput)) {
11287
- return;
11288
- }
11289
- for (const parameter of $pipelineJson.parameters) {
11290
- const isThisParameterResulting = $pipelineJson.tasks.some((task) => task.resultingParameterName === parameter.name);
11291
- if (!isThisParameterResulting) {
11292
- parameter.isInput = true;
11293
- // <- TODO: [💔] Why this is making typescript error in vscode but not in cli
11294
- // > Type 'true' is not assignable to type 'false'.ts(2322)
11295
- // > (property) isInput: false
11296
- // > The parameter is input of the pipeline The parameter is NOT input of the pipeline
11297
- }
11298
- }
11299
- }
11300
- /**
11301
- * Marks every non-input parameter as output when no output was declared.
11302
- *
11303
- * @private internal utility of `parsePipeline`
11304
- */
11305
- function markImplicitOutputParameters($pipelineJson) {
11306
- if ($pipelineJson.parameters.some((parameter) => parameter.isOutput)) {
11307
- return;
11308
- }
11309
- for (const parameter of $pipelineJson.parameters) {
11310
- if (!parameter.isInput) {
11311
- parameter.isOutput = true;
11312
- // <- TODO: [💔]
11313
- }
11314
- }
11315
- }
11316
- /**
11317
- * Removes `undefined` properties from serialized tasks and parameters.
11318
- *
11319
- * @private internal utility of `parsePipeline`
11320
- */
11321
- function removeUndefinedValuesFromPipeline($pipelineJson) {
11322
- $pipelineJson.tasks.forEach(removeUndefinedProperties);
11323
- $pipelineJson.parameters.forEach(removeUndefinedProperties);
11324
- }
11325
- /**
11326
- * Deletes all own properties with `undefined` values from a mutable JSON entity.
11327
- *
11328
- * @private internal utility of `parsePipeline`
11329
- */
11330
- function removeUndefinedProperties(entity) {
11331
- for (const [key, value] of Object.entries(entity)) {
11332
- if (value === undefined) {
11333
- delete entity[key];
11334
- }
11335
- }
11336
- }
11337
- /**
11338
- * Applies all sync-only high-level abstractions after parsing.
11318
+ * Extracts task dependencies and ensures referenced parameters exist.
11339
11319
  *
11340
- * @private internal utility of `parsePipeline`
11320
+ * @private internal utility of `processPipelineSection`
11341
11321
  */
11342
- function applySyncHighLevelAbstractions($pipelineJson) {
11343
- for (const highLevelAbstraction of HIGH_LEVEL_ABSTRACTIONS.filter(({ type }) => type === 'SYNC')) {
11344
- highLevelAbstraction.$applyToPipelineJson($pipelineJson);
11322
+ function registerTaskDependentParameters($taskJson, $pipelineJson) {
11323
+ $taskJson.dependentParameterNames = Array.from(extractParameterNamesFromTask($taskJson));
11324
+ for (const parameterName of $taskJson.dependentParameterNames) {
11325
+ // TODO: [🧠] This definition should be made first in the task
11326
+ defineParameter($pipelineJson, {
11327
+ parameterName,
11328
+ parameterDescription: null,
11329
+ isInput: false,
11330
+ isOutput: false,
11331
+ // <- Note: In this case null+false+false means that we do not know yet if it is input or output and we will set it later
11332
+ });
11345
11333
  }
11346
11334
  }
11347
11335
  /**
11348
- * Ensures parsed pipelines always have the default `GENERIC` formfactor.
11336
+ * Removes transient parsing flags and persists real tasks into the pipeline JSON.
11349
11337
  *
11350
- * @private internal utility of `parsePipeline`
11338
+ * @private internal utility of `processPipelineSection`
11351
11339
  */
11352
- function ensurePipelineFormfactor($pipelineJson) {
11353
- // Note: [🔆] If formfactor is still not set, set it to 'GENERIC'
11354
- if ($pipelineJson.formfactorName === undefined) {
11355
- $pipelineJson.formfactorName = 'GENERIC';
11340
+ function persistTaskIfNeeded($taskJson, $pipelineJson) {
11341
+ /*
11342
+ // TODO: [🍧] This should be checked in `MODEL` command + better error message
11343
+ if ($taskJson.taskType !== 'PROMPT_TASK' && $taskJson.modelRequirements !== undefined) {
11344
+ throw new UnexpectedError(
11345
+ spaceTrim(
11346
+ (block) => `
11347
+ Model requirements are defined for the block type ${
11348
+ $taskJson.taskType
11349
+ } which is not a \`PROMPT\` task
11350
+
11351
+ This should be avoided by the \`modelCommandParser\`
11352
+
11353
+ ${block(getPipelineIdentification($pipelineJson))}
11354
+ `,
11355
+ ),
11356
+ );
11357
+ }
11358
+ */
11359
+ if (!$taskJson.isTask) {
11360
+ return;
11356
11361
  }
11362
+ delete $taskJson.isSectionTypeSet;
11363
+ delete $taskJson.isTask;
11364
+ // TODO: [🍙] Maybe do reorder of `$taskJson` here
11365
+ $pipelineJson.tasks.push($taskJson);
11357
11366
  }
11367
+
11358
11368
  /**
11359
- * Finalizes ordering and exports the parsed pipeline JSON.
11369
+ * Compile pipeline from string (markdown) format to JSON format synchronously
11360
11370
  *
11361
- * @private internal utility of `parsePipeline`
11371
+ * Note: There are 3 similar functions:
11372
+ * - `compilePipeline` **(preferred)** - which properly compiles the promptbook and uses embedding for external knowledge
11373
+ * - `parsePipeline` - use only if you need to compile promptbook synchronously and it contains NO external knowledge
11374
+ * - `preparePipeline` - just one step in the compilation process
11375
+ *
11376
+ * Note: This function does not validate logic of the pipeline only the parsing
11377
+ * Note: This function acts as compilation process
11378
+ *
11379
+ * @param pipelineString {Promptbook} in string markdown format (.book.md)
11380
+ * @returns {Promptbook} compiled in JSON format (.bookc)
11381
+ * @throws {ParseError} if the promptbook string is not valid
11382
+ *
11383
+ * @public exported from `@promptbook/core`
11362
11384
  */
11363
- function exportParsedPipelineJson($pipelineJson) {
11364
- return exportJson({
11365
- name: 'pipelineJson',
11366
- message: `Result of \`parsePipeline\``,
11367
- order: ORDER_OF_PIPELINE_JSON,
11368
- value: {
11369
- formfactorName: 'GENERIC',
11370
- // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
11371
- ...$pipelineJson,
11372
- },
11373
- });
11385
+ function parsePipeline(pipelineString) {
11386
+ const $pipelineJson = createInitialPipelineJson(pipelineString);
11387
+ const preparedPipelineString = preparePipelineString(pipelineString, $pipelineJson);
11388
+ const { pipelineHead, pipelineSections } = parsePreparedPipelineSections(preparedPipelineString, $pipelineJson);
11389
+ const getUniqueSectionName = createUniqueSectionNameResolver(pipelineSections);
11390
+ applyPipelineHead(pipelineHead, $pipelineJson);
11391
+ for (const pipelineSection of pipelineSections) {
11392
+ processPipelineSection(pipelineSection, $pipelineJson, getUniqueSectionName);
11393
+ }
11394
+ return finalizeParsedPipeline($pipelineJson);
11374
11395
  }
11375
11396
  // TODO: [🧠] Maybe more things here can be refactored as high-level abstractions
11376
11397
  // TODO: [main] !!4 Warn if used only sync version
11377
11398
  // TODO: [🚞] Report here line/column of error
11378
- // TODO: Use spaceTrim more effectively
11379
- // TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
11380
- // TODO: [🥞] Not optimal parsing because `splitMarkdownIntoSections` is executed twice with same string, once through `flattenMarkdown` and second directly here
11381
- // TODO: [♈] Probably move expectations from tasks to parameters
11382
- // TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
11383
- // TODO: [🍙] Make some standard order of json properties
11384
11399
 
11385
11400
  /**
11386
11401
  * Compile pipeline from string (markdown) format to JSON format
@@ -18128,7 +18143,9 @@ const octopus3dAvatarVisual = {
18128
18143
  };
18129
18144
  const mantleRadiusX = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.horizontalStretch;
18130
18145
  const mantleRadiusY = size * morphologyProfile.body.bodyRadiusRatio * morphologyProfile.body.verticalStretch * 1.1;
18131
- const mantleRadiusZ = size * morphologyProfile.body.bodyRadiusRatio * (0.9 + (morphologyProfile.body.horizontalStretch - 1) * 0.3);
18146
+ const mantleRadiusZ = size *
18147
+ morphologyProfile.body.bodyRadiusRatio *
18148
+ (0.9 + (morphologyProfile.body.horizontalStretch - 1) * 0.3);
18132
18149
  const underbodyRadiusX = mantleRadiusX * (0.9 + (morphologyProfile.tentacles.rootSpreadScale - 1) * 0.08);
18133
18150
  const underbodyRadiusY = mantleRadiusY * (0.44 + morphologyProfile.body.lowerDropRatio * 3.1);
18134
18151
  const underbodyRadiusZ = mantleRadiusZ * 0.78;
@@ -18214,7 +18231,11 @@ const octopus3dAvatarVisual = {
18214
18231
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, faceEyeSpacing, faceEyeYOffset),
18215
18232
  }, faceEyeRadiusX, faceEyeRadiusY, mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, size, palette, timeMs, animationPhase + 0.7 + eyeRandom() * 0.6, interaction, morphologyProfile.face.eyeStyle);
18216
18233
  drawProjectedMouth(context, [
18217
- { x: -mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -mouthHalfWidth, mouthY) },
18234
+ {
18235
+ x: -mouthHalfWidth,
18236
+ y: mouthY,
18237
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, -mouthHalfWidth, mouthY),
18238
+ },
18218
18239
  {
18219
18240
  x: size * morphologyProfile.face.mouthCenterOffsetRatio,
18220
18241
  y: mouthY +
@@ -18223,7 +18244,11 @@ const octopus3dAvatarVisual = {
18223
18244
  interaction.gazeY * size * 0.01,
18224
18245
  z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, size * morphologyProfile.face.mouthCenterOffsetRatio, mouthY),
18225
18246
  },
18226
- { x: mouthHalfWidth, y: mouthY, z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY) },
18247
+ {
18248
+ x: mouthHalfWidth,
18249
+ y: mouthY,
18250
+ z: resolveEllipsoidSurfaceDepth(mantleRadiusX, mantleRadiusY, mantleRadiusZ, mouthHalfWidth, mouthY),
18251
+ },
18227
18252
  ], mantleCenter, headPitch, headYaw, sceneCenterX, sceneCenterY, palette, size);
18228
18253
  },
18229
18254
  };
@@ -18384,7 +18409,8 @@ function createOctopusTentacleStrokes(options) {
18384
18409
  z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.3 + sway * size * 0.012,
18385
18410
  };
18386
18411
  const controlPointTwo = {
18387
- x: anchorPoint.x + Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
18412
+ x: anchorPoint.x +
18413
+ Math.sin(orbitAngle) * lateralReach * (0.82 + morphologyProfile.tentacles.swayScale * 0.12),
18388
18414
  y: anchorPoint.y + flowLength * 0.66,
18389
18415
  z: anchorPoint.z + Math.cos(orbitAngle) * depthReach * 0.72 + sway * size * 0.02,
18390
18416
  };
@@ -18449,8 +18475,7 @@ function drawTentacleStroke(context, tentacleStroke, palette) {
18449
18475
  context.beginPath();
18450
18476
  context.moveTo(startPoint.x, startPoint.y);
18451
18477
  context.lineTo(endPoint.x, endPoint.y);
18452
- context.strokeStyle =
18453
- tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
18478
+ context.strokeStyle = tentacleStroke.colorBias > 0.6 ? `${palette.secondary}f0` : `${palette.primary}f0`;
18454
18479
  context.lineWidth = width;
18455
18480
  context.lineCap = 'round';
18456
18481
  context.stroke();
@@ -26645,6 +26670,212 @@ const DEFAULT_LIST_TIMEOUTS_LIMIT = 20;
26645
26670
  * @private internal USE TIMEOUT constant
26646
26671
  */
26647
26672
  const MAX_LIST_TIMEOUTS_LIMIT = 100;
26673
+ /**
26674
+ * Creates one formatted timeout-argument validation error.
26675
+ *
26676
+ * @private internal utility of USE TIMEOUT
26677
+ */
26678
+ function createTimeoutToolArgsError(message) {
26679
+ return new PipelineExecutionError(spaceTrim$1(`
26680
+ ${message}
26681
+ `));
26682
+ }
26683
+ /**
26684
+ * Normalizes one optional timeout id string.
26685
+ *
26686
+ * @private internal utility of USE TIMEOUT
26687
+ */
26688
+ function normalizeOptionalTimeoutId(value) {
26689
+ return typeof value === 'string' ? value.trim() : '';
26690
+ }
26691
+ /**
26692
+ * Parses timeout target selection for tools that accept either `timeoutId` or `allActive: true`.
26693
+ *
26694
+ * @private internal utility of USE TIMEOUT
26695
+ */
26696
+ function parseTimeoutTargetSelection(args, options) {
26697
+ const timeoutId = normalizeOptionalTimeoutId(args.timeoutId);
26698
+ const allActive = args.allActive === true;
26699
+ if (timeoutId && allActive) {
26700
+ throw createTimeoutToolArgsError(options.bothMessage);
26701
+ }
26702
+ if (allActive) {
26703
+ return { allActive: true };
26704
+ }
26705
+ if (!timeoutId) {
26706
+ throw createTimeoutToolArgsError(options.missingMessage);
26707
+ }
26708
+ return {
26709
+ timeoutId,
26710
+ allActive: false,
26711
+ };
26712
+ }
26713
+ /**
26714
+ * Parses one explicit `dueAt` update value.
26715
+ *
26716
+ * @private internal utility of USE TIMEOUT
26717
+ */
26718
+ function parseOptionalTimeoutDueAt(value) {
26719
+ if (typeof value !== 'string' || value.trim().length === 0) {
26720
+ return undefined;
26721
+ }
26722
+ const normalizedDueAt = value.trim();
26723
+ const dueAtTimestamp = Date.parse(normalizedDueAt);
26724
+ if (!Number.isFinite(dueAtTimestamp)) {
26725
+ throw createTimeoutToolArgsError('Timeout `dueAt` must be one valid ISO timestamp.');
26726
+ }
26727
+ return new Date(dueAtTimestamp).toISOString();
26728
+ }
26729
+ /**
26730
+ * Parses one explicit `extendByMs` update value.
26731
+ *
26732
+ * @private internal utility of USE TIMEOUT
26733
+ */
26734
+ function parseOptionalTimeoutExtendByMs(value) {
26735
+ if (typeof value !== 'number') {
26736
+ return undefined;
26737
+ }
26738
+ if (!Number.isFinite(value) || value <= 0) {
26739
+ throw createTimeoutToolArgsError('Timeout `extendByMs` must be a positive number of milliseconds.');
26740
+ }
26741
+ return Math.floor(value);
26742
+ }
26743
+ /**
26744
+ * Parses one explicit `recurrenceIntervalMs` update value.
26745
+ *
26746
+ * @private internal utility of USE TIMEOUT
26747
+ */
26748
+ function parseOptionalTimeoutRecurrenceInterval(value) {
26749
+ if (value === null) {
26750
+ return null;
26751
+ }
26752
+ if (typeof value !== 'number') {
26753
+ return undefined;
26754
+ }
26755
+ if (!Number.isFinite(value) || value <= 0) {
26756
+ throw createTimeoutToolArgsError('Timeout `recurrenceIntervalMs` must be a positive number of milliseconds or `null`.');
26757
+ }
26758
+ return Math.floor(value);
26759
+ }
26760
+ /**
26761
+ * Parses one explicit `message` update value.
26762
+ *
26763
+ * @private internal utility of USE TIMEOUT
26764
+ */
26765
+ function parseOptionalTimeoutMessage(value) {
26766
+ if (value === null) {
26767
+ return null;
26768
+ }
26769
+ if (typeof value !== 'string') {
26770
+ return undefined;
26771
+ }
26772
+ const normalizedMessage = value.trim();
26773
+ return normalizedMessage.length > 0 ? normalizedMessage : null;
26774
+ }
26775
+ /**
26776
+ * Parses one explicit `parameters` update value.
26777
+ *
26778
+ * @private internal utility of USE TIMEOUT
26779
+ */
26780
+ function parseOptionalTimeoutParameters(value) {
26781
+ if (value === undefined) {
26782
+ return undefined;
26783
+ }
26784
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
26785
+ throw createTimeoutToolArgsError('Timeout `parameters` must be one JSON object.');
26786
+ }
26787
+ return value;
26788
+ }
26789
+ /**
26790
+ * Parses one explicit `paused` update value.
26791
+ *
26792
+ * @private internal utility of USE TIMEOUT
26793
+ */
26794
+ function parseOptionalTimeoutPaused(value) {
26795
+ return typeof value === 'boolean' ? value : undefined;
26796
+ }
26797
+ /**
26798
+ * Parses patch fields for `update_timeout`.
26799
+ *
26800
+ * @private internal utility of USE TIMEOUT
26801
+ */
26802
+ function parseTimeoutUpdatePatch(args) {
26803
+ const patch = {};
26804
+ const dueAt = parseOptionalTimeoutDueAt(args.dueAt);
26805
+ const extendByMs = parseOptionalTimeoutExtendByMs(args.extendByMs);
26806
+ const recurrenceIntervalMs = parseOptionalTimeoutRecurrenceInterval(args.recurrenceIntervalMs);
26807
+ const message = parseOptionalTimeoutMessage(args.message);
26808
+ const parameters = parseOptionalTimeoutParameters(args.parameters);
26809
+ const paused = parseOptionalTimeoutPaused(args.paused);
26810
+ if (dueAt !== undefined) {
26811
+ patch.dueAt = dueAt;
26812
+ }
26813
+ if (extendByMs !== undefined) {
26814
+ patch.extendByMs = extendByMs;
26815
+ }
26816
+ if (patch.dueAt !== undefined && patch.extendByMs !== undefined) {
26817
+ throw createTimeoutToolArgsError('Timeout update cannot include both `dueAt` and `extendByMs`.');
26818
+ }
26819
+ if (recurrenceIntervalMs !== undefined) {
26820
+ patch.recurrenceIntervalMs = recurrenceIntervalMs;
26821
+ }
26822
+ if (message !== undefined) {
26823
+ patch.message = message;
26824
+ }
26825
+ if (parameters !== undefined) {
26826
+ patch.parameters = parameters;
26827
+ }
26828
+ if (paused !== undefined) {
26829
+ patch.paused = paused;
26830
+ }
26831
+ return patch;
26832
+ }
26833
+ /**
26834
+ * Determines whether the patch contains fields that are only supported for single-timeout updates.
26835
+ *
26836
+ * @private internal utility of USE TIMEOUT
26837
+ */
26838
+ function hasSingleTimeoutOnlyPatchFields(patch) {
26839
+ return (patch.dueAt !== undefined ||
26840
+ patch.extendByMs !== undefined ||
26841
+ patch.recurrenceIntervalMs !== undefined ||
26842
+ patch.message !== undefined ||
26843
+ patch.parameters !== undefined);
26844
+ }
26845
+ /**
26846
+ * Parses bulk timeout update arguments.
26847
+ *
26848
+ * @private internal utility of USE TIMEOUT
26849
+ */
26850
+ function parseBulkTimeoutUpdateArgs(patch) {
26851
+ if (patch.paused === undefined) {
26852
+ throw createTimeoutToolArgsError('Bulk timeout update with `allActive: true` requires `paused` to be explicitly set.');
26853
+ }
26854
+ if (hasSingleTimeoutOnlyPatchFields(patch)) {
26855
+ throw createTimeoutToolArgsError('Bulk timeout update only supports the `paused` field.');
26856
+ }
26857
+ return {
26858
+ allActive: true,
26859
+ paused: patch.paused,
26860
+ };
26861
+ }
26862
+ /**
26863
+ * Parses single-timeout update arguments.
26864
+ *
26865
+ * @private internal utility of USE TIMEOUT
26866
+ */
26867
+ function parseSingleTimeoutUpdateArgs(timeoutId, patch) {
26868
+ if (!timeoutId) {
26869
+ throw createTimeoutToolArgsError('Timeout `timeoutId` is required for single-timeout updates.');
26870
+ }
26871
+ if (Object.keys(patch).length === 0) {
26872
+ throw createTimeoutToolArgsError('Timeout update must include at least one editable field.');
26873
+ }
26874
+ return {
26875
+ timeoutId,
26876
+ patch,
26877
+ };
26878
+ }
26648
26879
  /**
26649
26880
  * Parses and validates `USE TIMEOUT` tool arguments.
26650
26881
  *
@@ -26671,22 +26902,14 @@ const parseTimeoutToolArgs = {
26671
26902
  * Parses `cancel_timeout` input.
26672
26903
  */
26673
26904
  cancel(args) {
26674
- const timeoutId = typeof args.timeoutId === 'string' ? args.timeoutId.trim() : '';
26675
- const allActive = args.allActive === true;
26676
- if (timeoutId && allActive) {
26677
- throw new PipelineExecutionError(spaceTrim$1(`
26678
- Timeout cancellation must target either one \`timeoutId\` or \`allActive: true\`, not both.
26679
- `));
26680
- }
26681
- if (allActive) {
26905
+ const target = parseTimeoutTargetSelection(args, {
26906
+ bothMessage: 'Timeout cancellation must target either one `timeoutId` or `allActive: true`, not both.',
26907
+ missingMessage: 'Timeout `timeoutId` is required unless you pass `allActive: true`.',
26908
+ });
26909
+ if (target.allActive) {
26682
26910
  return { allActive: true };
26683
26911
  }
26684
- if (!timeoutId) {
26685
- throw new PipelineExecutionError(spaceTrim$1(`
26686
- Timeout \`timeoutId\` is required unless you pass \`allActive: true\`.
26687
- `));
26688
- }
26689
- return { timeoutId };
26912
+ return { timeoutId: target.timeoutId };
26690
26913
  },
26691
26914
  /**
26692
26915
  * Parses `list_timeouts` input.
@@ -26717,106 +26940,14 @@ const parseTimeoutToolArgs = {
26717
26940
  * Parses `update_timeout` input.
26718
26941
  */
26719
26942
  update(args) {
26720
- const timeoutId = typeof args.timeoutId === 'string' ? args.timeoutId.trim() : '';
26721
- const allActive = args.allActive === true;
26722
- if (timeoutId && allActive) {
26723
- throw new PipelineExecutionError(spaceTrim$1(`
26724
- Timeout update must target either one \`timeoutId\` or \`allActive: true\`, not both.
26725
- `));
26726
- }
26727
- if (!timeoutId && !allActive) {
26728
- throw new PipelineExecutionError(spaceTrim$1(`
26729
- Timeout update requires one \`timeoutId\` or \`allActive: true\`.
26730
- `));
26731
- }
26732
- const patch = {};
26733
- if (typeof args.dueAt === 'string' && args.dueAt.trim().length > 0) {
26734
- const normalizedDueAt = args.dueAt.trim();
26735
- const dueAtTimestamp = Date.parse(normalizedDueAt);
26736
- if (!Number.isFinite(dueAtTimestamp)) {
26737
- throw new PipelineExecutionError(spaceTrim$1(`
26738
- Timeout \`dueAt\` must be one valid ISO timestamp.
26739
- `));
26740
- }
26741
- patch.dueAt = new Date(dueAtTimestamp).toISOString();
26742
- }
26743
- if (typeof args.extendByMs === 'number') {
26744
- if (!Number.isFinite(args.extendByMs) || args.extendByMs <= 0) {
26745
- throw new PipelineExecutionError(spaceTrim$1(`
26746
- Timeout \`extendByMs\` must be a positive number of milliseconds.
26747
- `));
26748
- }
26749
- patch.extendByMs = Math.floor(args.extendByMs);
26750
- }
26751
- if (patch.dueAt !== undefined && patch.extendByMs !== undefined) {
26752
- throw new PipelineExecutionError(spaceTrim$1(`
26753
- Timeout update cannot include both \`dueAt\` and \`extendByMs\`.
26754
- `));
26755
- }
26756
- if (args.recurrenceIntervalMs === null) {
26757
- patch.recurrenceIntervalMs = null;
26758
- }
26759
- else if (typeof args.recurrenceIntervalMs === 'number') {
26760
- if (!Number.isFinite(args.recurrenceIntervalMs) || args.recurrenceIntervalMs <= 0) {
26761
- throw new PipelineExecutionError(spaceTrim$1(`
26762
- Timeout \`recurrenceIntervalMs\` must be a positive number of milliseconds or \`null\`.
26763
- `));
26764
- }
26765
- patch.recurrenceIntervalMs = Math.floor(args.recurrenceIntervalMs);
26766
- }
26767
- if (args.message === null) {
26768
- patch.message = null;
26769
- }
26770
- else if (typeof args.message === 'string') {
26771
- const normalizedMessage = args.message.trim();
26772
- patch.message = normalizedMessage.length > 0 ? normalizedMessage : null;
26773
- }
26774
- if (args.parameters !== undefined) {
26775
- if (!args.parameters || typeof args.parameters !== 'object' || Array.isArray(args.parameters)) {
26776
- throw new PipelineExecutionError(spaceTrim$1(`
26777
- Timeout \`parameters\` must be one JSON object.
26778
- `));
26779
- }
26780
- patch.parameters = args.parameters;
26781
- }
26782
- if (typeof args.paused === 'boolean') {
26783
- patch.paused = args.paused;
26784
- }
26785
- if (allActive) {
26786
- if (patch.paused === undefined) {
26787
- throw new PipelineExecutionError(spaceTrim$1(`
26788
- Bulk timeout update with \`allActive: true\` requires \`paused\` to be explicitly set.
26789
- `));
26790
- }
26791
- const hasSingleOnlyPatch = patch.dueAt !== undefined ||
26792
- patch.extendByMs !== undefined ||
26793
- patch.recurrenceIntervalMs !== undefined ||
26794
- patch.message !== undefined ||
26795
- patch.parameters !== undefined;
26796
- if (hasSingleOnlyPatch) {
26797
- throw new PipelineExecutionError(spaceTrim$1(`
26798
- Bulk timeout update only supports the \`paused\` field.
26799
- `));
26800
- }
26801
- return {
26802
- allActive: true,
26803
- paused: patch.paused,
26804
- };
26805
- }
26806
- if (!timeoutId) {
26807
- throw new PipelineExecutionError(spaceTrim$1(`
26808
- Timeout \`timeoutId\` is required for single-timeout updates.
26809
- `));
26810
- }
26811
- if (Object.keys(patch).length === 0) {
26812
- throw new PipelineExecutionError(spaceTrim$1(`
26813
- Timeout update must include at least one editable field.
26814
- `));
26815
- }
26816
- return {
26817
- timeoutId,
26818
- patch,
26819
- };
26943
+ const target = parseTimeoutTargetSelection(args, {
26944
+ bothMessage: 'Timeout update must target either one `timeoutId` or `allActive: true`, not both.',
26945
+ missingMessage: 'Timeout update requires one `timeoutId` or `allActive: true`.',
26946
+ });
26947
+ const patch = parseTimeoutUpdatePatch(args);
26948
+ return target.allActive
26949
+ ? parseBulkTimeoutUpdateArgs(patch)
26950
+ : parseSingleTimeoutUpdateArgs(target.timeoutId, patch);
26820
26951
  },
26821
26952
  };
26822
26953