@promptbook/markdown-utils 0.112.0-71 → 0.112.0-73

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 (195) hide show
  1. package/esm/index.es.js +1434 -808
  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/save/_common/chatExportRendering.d.ts +28 -0
  5. package/esm/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  6. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +1 -1
  7. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  8. package/esm/src/book-components/Chat/save/index.d.ts +4 -4
  9. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +1 -1
  10. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  11. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -2
  12. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +2 -2
  13. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  14. package/esm/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  15. package/esm/src/book-components/Chat/utils/getToolCallChipletInfo.test.d.ts +1 -0
  16. package/esm/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  17. package/esm/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +14 -2
  18. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +2 -0
  19. package/esm/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  20. package/esm/src/cli/cli-commands/run/prepareRunCommandResources.d.ts +20 -0
  21. package/esm/src/cli/cli-commands/run/resolveRunInputParameters.d.ts +12 -0
  22. package/esm/src/cli/cli-commands/run/runCommandAction.d.ts +21 -0
  23. package/esm/src/cli/cli-commands/run/runPipelineExecution.d.ts +14 -0
  24. package/esm/src/cli/cli-commands/run.d.ts +1 -1
  25. package/esm/src/conversion/parsePipeline/applyPipelineHead.d.ts +8 -0
  26. package/esm/src/conversion/parsePipeline/createInitialPipelineJson.d.ts +8 -0
  27. package/esm/src/conversion/parsePipeline/createUniqueSectionNameResolver.d.ts +14 -0
  28. package/esm/src/conversion/parsePipeline/defineParameter.d.ts +8 -0
  29. package/esm/src/conversion/parsePipeline/extractPipelineDescription.d.ts +6 -0
  30. package/esm/src/conversion/parsePipeline/finalizeParsedPipeline.d.ts +8 -0
  31. package/esm/src/conversion/parsePipeline/getPipelineIdentification.d.ts +7 -0
  32. package/esm/src/conversion/parsePipeline/parsePreparedPipelineSections.d.ts +18 -0
  33. package/esm/src/conversion/parsePipeline/preparePipelineString.d.ts +8 -0
  34. package/esm/src/conversion/parsePipeline/processPipelineSection.d.ts +9 -0
  35. package/esm/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  36. package/esm/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  37. package/esm/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  38. package/esm/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  39. package/esm/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  40. package/esm/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  41. package/esm/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  42. package/esm/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  43. package/esm/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  44. package/esm/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  45. package/esm/src/execution/resolveTaskTldr.d.ts +32 -0
  46. package/esm/src/execution/resolveTaskTldr.test.d.ts +1 -0
  47. package/esm/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  48. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  49. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  50. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  51. package/esm/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  52. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  53. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  54. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  55. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  56. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  57. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  58. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  59. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  60. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  61. package/esm/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  62. package/esm/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  63. package/esm/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  64. package/esm/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  65. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  66. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  67. package/esm/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  68. package/esm/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  69. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  70. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  71. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  72. package/esm/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  73. package/esm/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  74. package/esm/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  75. package/esm/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  76. package/esm/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  77. package/esm/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  78. package/esm/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  79. package/esm/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  80. package/esm/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  81. package/esm/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  82. package/esm/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  83. package/esm/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  84. package/esm/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  85. package/esm/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  86. package/esm/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  87. package/esm/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  88. package/esm/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  89. package/esm/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  90. package/esm/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  91. package/esm/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  92. package/esm/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  93. package/esm/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  94. package/esm/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  95. package/esm/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  96. package/esm/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  97. package/esm/src/version.d.ts +1 -1
  98. package/package.json +1 -1
  99. package/umd/index.umd.js +1434 -808
  100. package/umd/index.umd.js.map +1 -1
  101. package/umd/src/book-3.0/Book.d.ts +6 -0
  102. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +28 -0
  103. package/umd/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  104. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +1 -1
  105. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  106. package/umd/src/book-components/Chat/save/index.d.ts +4 -4
  107. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +1 -1
  108. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  109. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -2
  110. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +2 -2
  111. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  112. package/umd/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  113. package/umd/src/book-components/Chat/utils/getToolCallChipletInfo.test.d.ts +1 -0
  114. package/umd/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  115. package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +14 -2
  116. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +2 -0
  117. package/umd/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  118. package/umd/src/cli/cli-commands/run/prepareRunCommandResources.d.ts +20 -0
  119. package/umd/src/cli/cli-commands/run/resolveRunInputParameters.d.ts +12 -0
  120. package/umd/src/cli/cli-commands/run/runCommandAction.d.ts +21 -0
  121. package/umd/src/cli/cli-commands/run/runPipelineExecution.d.ts +14 -0
  122. package/umd/src/cli/cli-commands/run.d.ts +1 -1
  123. package/umd/src/conversion/parsePipeline/applyPipelineHead.d.ts +8 -0
  124. package/umd/src/conversion/parsePipeline/createInitialPipelineJson.d.ts +8 -0
  125. package/umd/src/conversion/parsePipeline/createUniqueSectionNameResolver.d.ts +14 -0
  126. package/umd/src/conversion/parsePipeline/defineParameter.d.ts +8 -0
  127. package/umd/src/conversion/parsePipeline/extractPipelineDescription.d.ts +6 -0
  128. package/umd/src/conversion/parsePipeline/finalizeParsedPipeline.d.ts +8 -0
  129. package/umd/src/conversion/parsePipeline/getPipelineIdentification.d.ts +7 -0
  130. package/umd/src/conversion/parsePipeline/parsePreparedPipelineSections.d.ts +18 -0
  131. package/umd/src/conversion/parsePipeline/preparePipelineString.d.ts +8 -0
  132. package/umd/src/conversion/parsePipeline/processPipelineSection.d.ts +9 -0
  133. package/umd/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  134. package/umd/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  135. package/umd/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  136. package/umd/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  137. package/umd/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  138. package/umd/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  139. package/umd/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  140. package/umd/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  141. package/umd/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  142. package/umd/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  143. package/umd/src/execution/resolveTaskTldr.d.ts +32 -0
  144. package/umd/src/execution/resolveTaskTldr.test.d.ts +1 -0
  145. package/umd/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  146. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  147. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  148. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  149. package/umd/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  150. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  151. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  152. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  153. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  154. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  155. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  156. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  157. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  158. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  159. package/umd/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  160. package/umd/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  161. package/umd/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  162. package/umd/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  163. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  164. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  165. package/umd/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  166. package/umd/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  167. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  168. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  169. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  170. package/umd/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  171. package/umd/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  172. package/umd/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  173. package/umd/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  174. package/umd/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  175. package/umd/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  176. package/umd/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  177. package/umd/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  178. package/umd/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  179. package/umd/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  180. package/umd/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  181. package/umd/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  182. package/umd/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  183. package/umd/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  184. package/umd/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  185. package/umd/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  186. package/umd/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  187. package/umd/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  188. package/umd/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  189. package/umd/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  190. package/umd/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  191. package/umd/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  192. package/umd/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  193. package/umd/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  194. package/umd/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  195. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -23,7 +23,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
23
23
  * @generated
24
24
  * @see https://github.com/webgptorg/promptbook
25
25
  */
26
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-71';
26
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-73';
27
27
  /**
28
28
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
29
29
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1691,6 +1691,60 @@ function validatePipelineString(pipelineString) {
1691
1691
  }
1692
1692
  // TODO: [🧠][🈴] Where is the best location for this file
1693
1693
 
1694
+ /**
1695
+ * Appends one markdown block to an existing markdown document.
1696
+ *
1697
+ * @private internal utility of `pipelineJsonToString`
1698
+ */
1699
+ function appendMarkdownBlock(pipelineString, markdownBlock) {
1700
+ return spaceTrim$1((block) => `
1701
+ ${block(pipelineString)}
1702
+
1703
+ ${block(markdownBlock)}
1704
+ `);
1705
+ }
1706
+
1707
+ /**
1708
+ * Collects pipeline-level commands in the existing serialization order.
1709
+ *
1710
+ * @private internal utility of `pipelineJsonToString`
1711
+ */
1712
+ function createPipelineCommands(pipelineJson) {
1713
+ const { pipelineUrl, bookVersion, parameters } = pipelineJson;
1714
+ const commands = [];
1715
+ if (pipelineUrl) {
1716
+ commands.push(`PIPELINE URL ${pipelineUrl}`);
1717
+ }
1718
+ if (bookVersion !== `undefined`) {
1719
+ commands.push(`BOOK VERSION ${bookVersion}`);
1720
+ }
1721
+ commands.push(...createParameterCommands(parameters, 'INPUT PARAMETER', ({ isInput }) => isInput));
1722
+ commands.push(...createParameterCommands(parameters, 'OUTPUT PARAMETER', ({ isOutput }) => isOutput));
1723
+ return commands;
1724
+ }
1725
+ /**
1726
+ * Builds one group of parameter commands while preserving the original parameter order.
1727
+ *
1728
+ * @private internal utility of `createPipelineCommands`
1729
+ */
1730
+ function createParameterCommands(parameters, commandPrefix, isIncluded) {
1731
+ return parameters
1732
+ .filter((parameter) => isIncluded(parameter))
1733
+ .map((parameter) => `${commandPrefix} ${parameterJsonToString(parameter)}`);
1734
+ }
1735
+ /**
1736
+ * Converts one parameter JSON declaration to the serialized inline form.
1737
+ *
1738
+ * @private internal utility of `createPipelineCommands`
1739
+ */
1740
+ function parameterJsonToString(parameterJson) {
1741
+ const { name, description } = parameterJson;
1742
+ if (!description) {
1743
+ return `{${name}}`;
1744
+ }
1745
+ return `{${name}} ${description}`;
1746
+ }
1747
+
1694
1748
  /**
1695
1749
  * Prettify the html code
1696
1750
  *
@@ -1705,141 +1759,211 @@ function prettifyMarkdown(content) {
1705
1759
  }
1706
1760
 
1707
1761
  /**
1708
- * Converts promptbook in JSON format to string format
1709
- *
1710
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1711
- * @param pipelineJson Promptbook in JSON format (.bookc)
1712
- * @returns Promptbook in string format (.book.md)
1762
+ * Creates the initial markdown heading and description of a pipeline.
1713
1763
  *
1714
- * @public exported from `@promptbook/core`
1764
+ * @private internal utility of `pipelineJsonToString`
1715
1765
  */
1716
- function pipelineJsonToString(pipelineJson) {
1717
- const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
1718
- let pipelineString = spaceTrim$1((block) => `
1766
+ function createPipelineIntroduction(pipelineJson) {
1767
+ const { title, description } = pipelineJson;
1768
+ const pipelineIntroduction = spaceTrim$1((block) => `
1719
1769
  # ${title}
1720
1770
 
1721
1771
  ${block(description || '')}
1722
1772
  `);
1773
+ // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1774
+ return prettifyMarkdown(pipelineIntroduction);
1775
+ }
1776
+
1777
+ /**
1778
+ * Renders commands as markdown bullet items.
1779
+ *
1780
+ * @private internal utility of `pipelineJsonToString`
1781
+ */
1782
+ function stringifyCommands(commands) {
1783
+ return commands.map((command) => `- ${command}`).join('\n');
1784
+ }
1785
+
1786
+ /**
1787
+ * Collects all task-specific serialization details.
1788
+ *
1789
+ * @private internal utility of `pipelineJsonToString`
1790
+ */
1791
+ function createTaskSerialization(task) {
1792
+ const taskTypeSerialization = createTaskTypeSerialization(task);
1793
+ return {
1794
+ commands: [
1795
+ ...taskTypeSerialization.commands,
1796
+ ...createJokerCommands(task),
1797
+ ...createPostprocessingCommands(task),
1798
+ ...createExpectationCommands(task),
1799
+ ...createFormatCommands(task),
1800
+ ],
1801
+ contentLanguage: taskTypeSerialization.contentLanguage,
1802
+ };
1803
+ }
1804
+ /**
1805
+ * Collects commands and content language driven by the task type.
1806
+ *
1807
+ * @private internal utility of `createTaskSerialization`
1808
+ */
1809
+ function createTaskTypeSerialization(task) {
1810
+ if (task.taskType === 'PROMPT_TASK') {
1811
+ return {
1812
+ commands: createPromptTaskCommands(task),
1813
+ contentLanguage: 'text',
1814
+ };
1815
+ }
1816
+ if (task.taskType === 'SIMPLE_TASK') {
1817
+ return {
1818
+ commands: ['SIMPLE TEMPLATE'],
1819
+ contentLanguage: 'text',
1820
+ };
1821
+ }
1822
+ if (task.taskType === 'SCRIPT_TASK') {
1823
+ return {
1824
+ commands: ['SCRIPT'],
1825
+ contentLanguage: task.contentLanguage || '',
1826
+ };
1827
+ }
1828
+ if (task.taskType === 'DIALOG_TASK') {
1829
+ return {
1830
+ commands: ['DIALOG'],
1831
+ contentLanguage: 'text',
1832
+ };
1833
+ }
1834
+ return {
1835
+ commands: [],
1836
+ contentLanguage: 'text',
1837
+ };
1838
+ }
1839
+ /**
1840
+ * Collects prompt-task-specific commands.
1841
+ *
1842
+ * @private internal utility of `createTaskSerialization`
1843
+ */
1844
+ function createPromptTaskCommands(task) {
1845
+ const { modelName, modelVariant } = task.modelRequirements || {};
1723
1846
  const commands = [];
1724
- if (pipelineUrl) {
1725
- commands.push(`PIPELINE URL ${pipelineUrl}`);
1847
+ // Note: Do nothing, it is default
1848
+ // commands.push(`PROMPT`);
1849
+ if (modelVariant) {
1850
+ commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1726
1851
  }
1727
- if (bookVersion !== `undefined`) {
1728
- commands.push(`BOOK VERSION ${bookVersion}`);
1852
+ if (modelName) {
1853
+ commands.push(`MODEL NAME \`${modelName}\``);
1729
1854
  }
1730
- // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1731
- pipelineString = prettifyMarkdown(pipelineString);
1732
- for (const parameter of parameters.filter(({ isInput }) => isInput)) {
1733
- commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
1855
+ return commands;
1856
+ }
1857
+ /**
1858
+ * Collects joker commands.
1859
+ *
1860
+ * @private internal utility of `createTaskSerialization`
1861
+ */
1862
+ function createJokerCommands(task) {
1863
+ var _a;
1864
+ return ((_a = task.jokerParameterNames) === null || _a === void 0 ? void 0 : _a.map((joker) => `JOKER {${joker}}`)) || [];
1865
+ }
1866
+ /**
1867
+ * Collects postprocessing commands.
1868
+ *
1869
+ * @private internal utility of `createTaskSerialization`
1870
+ */
1871
+ function createPostprocessingCommands(task) {
1872
+ var _a;
1873
+ return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
1874
+ }
1875
+ /**
1876
+ * Collects expectation commands.
1877
+ *
1878
+ * @private internal utility of `createTaskSerialization`
1879
+ */
1880
+ function createExpectationCommands(task) {
1881
+ if (!task.expectations) {
1882
+ return [];
1734
1883
  }
1735
- for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
1736
- commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
1884
+ return Object.entries(task.expectations).flatMap(([unit, expectation]) => createExpectationCommandsForUnit(unit, expectation.min, expectation.max));
1885
+ }
1886
+ /**
1887
+ * Collects expectation commands for a single unit.
1888
+ *
1889
+ * @private internal utility of `createTaskSerialization`
1890
+ */
1891
+ function createExpectationCommandsForUnit(unit, min, max) {
1892
+ if (min === max) {
1893
+ return [`EXPECT EXACTLY ${min} ${formatExpectationUnit(unit, min)}`];
1737
1894
  }
1738
- pipelineString = spaceTrim$1((block) => `
1739
- ${block(pipelineString)}
1740
-
1741
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
1742
- `);
1743
- for (const task of tasks) {
1744
- const {
1745
- /* Note: Not using:> name, */
1746
- title, description,
1747
- /* Note: dependentParameterNames, */
1748
- jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
1749
- const commands = [];
1750
- let contentLanguage = 'text';
1751
- if (taskType === 'PROMPT_TASK') {
1752
- const { modelRequirements } = task;
1753
- const { modelName, modelVariant } = modelRequirements || {};
1754
- // Note: Do nothing, it is default
1755
- // commands.push(`PROMPT`);
1756
- if (modelVariant) {
1757
- commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1758
- }
1759
- if (modelName) {
1760
- commands.push(`MODEL NAME \`${modelName}\``);
1761
- }
1762
- }
1763
- else if (taskType === 'SIMPLE_TASK') {
1764
- commands.push(`SIMPLE TEMPLATE`);
1765
- // Note: Nothing special here
1766
- }
1767
- else if (taskType === 'SCRIPT_TASK') {
1768
- commands.push(`SCRIPT`);
1769
- if (task.contentLanguage) {
1770
- contentLanguage = task.contentLanguage;
1771
- }
1772
- else {
1773
- contentLanguage = '';
1774
- }
1775
- }
1776
- else if (taskType === 'DIALOG_TASK') {
1777
- commands.push(`DIALOG`);
1778
- // Note: Nothing special here
1779
- } // <- }else if([🅱]
1780
- if (jokers) {
1781
- for (const joker of jokers) {
1782
- commands.push(`JOKER {${joker}}`);
1783
- }
1784
- } /* not else */
1785
- if (postprocessing) {
1786
- for (const postprocessingFunctionName of postprocessing) {
1787
- commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
1788
- }
1789
- } /* not else */
1790
- if (expectations) {
1791
- for (const [unit, { min, max }] of Object.entries(expectations)) {
1792
- if (min === max) {
1793
- commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1794
- }
1795
- else {
1796
- if (min !== undefined) {
1797
- commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1798
- } /* not else */
1799
- if (max !== undefined) {
1800
- commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
1801
- }
1802
- }
1803
- }
1804
- } /* not else */
1805
- if (format) {
1806
- if (format === 'JSON') {
1807
- // TODO: @deprecated remove
1808
- commands.push(`FORMAT JSON`);
1809
- }
1810
- } /* not else */
1811
- pipelineString = spaceTrim$1((block) => `
1812
- ${block(pipelineString)}
1895
+ const commands = [];
1896
+ if (min !== undefined) {
1897
+ commands.push(`EXPECT MIN ${min} ${formatExpectationUnit(unit, min)}`);
1898
+ }
1899
+ if (max !== undefined) {
1900
+ commands.push(`EXPECT MAX ${max} ${formatExpectationUnit(unit, max)}`);
1901
+ }
1902
+ return commands;
1903
+ }
1904
+ /**
1905
+ * Formats the expectation unit exactly as the legacy serializer does.
1906
+ *
1907
+ * @private internal utility of `createTaskSerialization`
1908
+ */
1909
+ function formatExpectationUnit(unit, amount) {
1910
+ return capitalize(unit + (amount > 1 ? 's' : ''));
1911
+ }
1912
+ /**
1913
+ * Collects format commands.
1914
+ *
1915
+ * @private internal utility of `createTaskSerialization`
1916
+ */
1917
+ function createFormatCommands(task) {
1918
+ if (task.format === 'JSON') {
1919
+ // TODO: @deprecated remove
1920
+ return ['FORMAT JSON'];
1921
+ }
1922
+ return [];
1923
+ }
1813
1924
 
1814
- ## ${title}
1925
+ /**
1926
+ * Stringifies one task section of the pipeline.
1927
+ *
1928
+ * @private internal utility of `pipelineJsonToString`
1929
+ */
1930
+ function stringifyTask(task) {
1931
+ const { title, description, content, resultingParameterName } = task;
1932
+ const { commands, contentLanguage } = createTaskSerialization(task);
1933
+ return spaceTrim$1((block) => `
1934
+ ## ${title}
1815
1935
 
1816
- ${block(description || '')}
1936
+ ${block(description || '')}
1817
1937
 
1818
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
1938
+ ${block(stringifyCommands(commands))}
1819
1939
 
1820
- \`\`\`${contentLanguage}
1821
- ${block(spaceTrim$1(content))}
1822
- \`\`\`
1940
+ \`\`\`${contentLanguage}
1941
+ ${block(spaceTrim$1(content))}
1942
+ \`\`\`
1823
1943
 
1824
- \`-> {${resultingParameterName}}\`
1825
- `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1826
- // <- TODO: [main] !!3 Escape
1827
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1828
- }
1829
- return validatePipelineString(pipelineString);
1944
+ \`-> {${resultingParameterName}}\`
1945
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1946
+ // <- TODO: [main] !!3 Escape
1947
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1830
1948
  }
1949
+
1831
1950
  /**
1832
- * Handles task parameter Json to string.
1951
+ * Converts promptbook in JSON format to string format
1833
1952
  *
1834
- * @private internal utility of `pipelineJsonToString`
1953
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1954
+ * @param pipelineJson Promptbook in JSON format (.bookc)
1955
+ * @returns Promptbook in string format (.book.md)
1956
+ *
1957
+ * @public exported from `@promptbook/core`
1835
1958
  */
1836
- function taskParameterJsonToString(taskParameterJson) {
1837
- const { name, description } = taskParameterJson;
1838
- let parameterString = `{${name}}`;
1839
- if (description) {
1840
- parameterString = `${parameterString} ${description}`;
1959
+ function pipelineJsonToString(pipelineJson) {
1960
+ let pipelineString = createPipelineIntroduction(pipelineJson);
1961
+ const pipelineCommands = createPipelineCommands(pipelineJson);
1962
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
1963
+ for (const task of pipelineJson.tasks) {
1964
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
1841
1965
  }
1842
- return parameterString;
1966
+ return validatePipelineString(pipelineString);
1843
1967
  }
1844
1968
  // TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
1845
1969
  // TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
@@ -2279,233 +2403,519 @@ function validatePipeline(pipeline) {
2279
2403
  */
2280
2404
  function validatePipeline_InnerFunction(pipeline) {
2281
2405
  // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
2282
- const pipelineIdentification = (() => {
2283
- // Note: This is a 😐 implementation of [🚞]
2284
- const _ = [];
2285
- if (pipeline.sourceFile !== undefined) {
2286
- _.push(`File: ${pipeline.sourceFile}`);
2287
- }
2288
- if (pipeline.pipelineUrl !== undefined) {
2289
- _.push(`Url: ${pipeline.pipelineUrl}`);
2406
+ const context = createPipelineValidationContext(pipeline);
2407
+ validatePipelineMetadata(context);
2408
+ validatePipelineCollectionsStructure(context);
2409
+ validatePipelineParameters(context);
2410
+ validatePipelineTasks(context);
2411
+ validatePipelineDependencyResolution(context);
2412
+ // Note: Check that formfactor is corresponding to the pipeline interface
2413
+ // TODO: !!6 Implement this
2414
+ // pipeline.formfactorName
2415
+ }
2416
+ /**
2417
+ * Creates the shared validation context for one pipeline.
2418
+ *
2419
+ * @private internal utility of `validatePipeline`
2420
+ */
2421
+ function createPipelineValidationContext(pipeline) {
2422
+ return {
2423
+ pipeline,
2424
+ pipelineIdentification: getPipelineIdentification(pipeline),
2425
+ };
2426
+ }
2427
+ /**
2428
+ * Builds a short file/url identification block for validation errors.
2429
+ *
2430
+ * @private internal utility of `validatePipeline`
2431
+ */
2432
+ function getPipelineIdentification(pipeline) {
2433
+ // Note: This is a 😐 implementation of [🚞]
2434
+ const pipelineIdentificationParts = [];
2435
+ if (pipeline.sourceFile !== undefined) {
2436
+ pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
2437
+ }
2438
+ if (pipeline.pipelineUrl !== undefined) {
2439
+ pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
2440
+ }
2441
+ return pipelineIdentificationParts.join('\n');
2442
+ }
2443
+ /**
2444
+ * Validates pipeline-level metadata fields.
2445
+ *
2446
+ * @private internal step of `validatePipeline`
2447
+ */
2448
+ function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
2449
+ validatePipelineUrl(pipeline, pipelineIdentification);
2450
+ validatePipelineBookVersion(pipeline, pipelineIdentification);
2451
+ }
2452
+ /**
2453
+ * Validates that the expected top-level collections have array structure.
2454
+ *
2455
+ * @private internal step of `validatePipeline`
2456
+ */
2457
+ function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification }) {
2458
+ validatePipelineParametersCollection(pipeline, pipelineIdentification);
2459
+ validatePipelineTasksCollection(pipeline, pipelineIdentification);
2460
+ }
2461
+ /**
2462
+ * Validates all pipeline parameter declarations.
2463
+ *
2464
+ * @private internal step of `validatePipeline`
2465
+ */
2466
+ function validatePipelineParameters({ pipeline, pipelineIdentification }) {
2467
+ for (const parameter of pipeline.parameters) {
2468
+ validatePipelineParameter(parameter, pipeline, pipelineIdentification);
2469
+ }
2470
+ }
2471
+ /**
2472
+ * Validates all pipeline tasks and their per-task invariants.
2473
+ *
2474
+ * @private internal step of `validatePipeline`
2475
+ */
2476
+ function validatePipelineTasks({ pipeline, pipelineIdentification }) {
2477
+ // Note: All input parameters are defined - so that they can be used as result of some task
2478
+ const definedParameters = createInitiallyDefinedParameters(pipeline);
2479
+ for (const task of pipeline.tasks) {
2480
+ validatePipelineTask(task, definedParameters, pipelineIdentification);
2481
+ }
2482
+ }
2483
+ /**
2484
+ * Validates that task dependencies can be resolved without cycles or missing definitions.
2485
+ *
2486
+ * @private internal step of `validatePipeline`
2487
+ */
2488
+ function validatePipelineDependencyResolution({ pipeline, pipelineIdentification }) {
2489
+ let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
2490
+ let loopLimit = LOOP_LIMIT;
2491
+ while (hasUnresolvedTasks(dependencyResolutionState)) {
2492
+ if (loopLimit-- < 0) {
2493
+ throw createDependencyResolutionLoopLimitError(pipelineIdentification);
2290
2494
  }
2291
- return _.join('\n');
2292
- })();
2293
- if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
2294
- // <- Note: [🚲]
2295
- throw new PipelineLogicError(spaceTrim$1((block) => `
2296
- Invalid promptbook URL "${pipeline.pipelineUrl}"
2297
-
2298
- ${block(pipelineIdentification)}
2299
- `));
2495
+ dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
2300
2496
  }
2301
- if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
2302
- // <- Note: [🚲]
2303
- throw new PipelineLogicError(spaceTrim$1((block) => `
2304
- Invalid Promptbook Version "${pipeline.bookVersion}"
2305
-
2306
- ${block(pipelineIdentification)}
2307
- `));
2497
+ }
2498
+ /**
2499
+ * Validates one pipeline parameter declaration.
2500
+ *
2501
+ * @private internal step of `validatePipeline`
2502
+ */
2503
+ function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
2504
+ validateParameterDirection(parameter, pipelineIdentification);
2505
+ validateParameterUsage(parameter, pipeline, pipelineIdentification);
2506
+ validateParameterDefinition(parameter, pipeline, pipelineIdentification);
2507
+ }
2508
+ /**
2509
+ * Validates one pipeline task and its invariants.
2510
+ *
2511
+ * @private internal step of `validatePipeline`
2512
+ */
2513
+ function validatePipelineTask(task, definedParameters, pipelineIdentification) {
2514
+ validateTaskResultingParameter(task, definedParameters, pipelineIdentification);
2515
+ validateTaskJokers(task, pipelineIdentification);
2516
+ validateTaskExpectations(task, pipelineIdentification);
2517
+ }
2518
+ /**
2519
+ * Validates the pipeline URL, when present.
2520
+ *
2521
+ * @private internal utility of `validatePipeline`
2522
+ */
2523
+ function validatePipelineUrl(pipeline, pipelineIdentification) {
2524
+ if (pipeline.pipelineUrl === undefined || isValidPipelineUrl(pipeline.pipelineUrl)) {
2525
+ return;
2308
2526
  }
2309
- // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2310
- if (!Array.isArray(pipeline.parameters)) {
2311
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2312
- throw new ParseError(spaceTrim$1((block) => `
2313
- Pipeline is valid JSON but with wrong structure
2527
+ // <- Note: [🚲]
2528
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2529
+ Invalid promptbook URL "${pipeline.pipelineUrl}"
2314
2530
 
2315
- \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2316
-
2317
- ${block(pipelineIdentification)}
2318
- `));
2531
+ ${block(pipelineIdentification)}
2532
+ `));
2533
+ }
2534
+ /**
2535
+ * Validates the Promptbook version, when present.
2536
+ *
2537
+ * @private internal utility of `validatePipeline`
2538
+ */
2539
+ function validatePipelineBookVersion(pipeline, pipelineIdentification) {
2540
+ if (pipeline.bookVersion === undefined || isValidPromptbookVersion(pipeline.bookVersion)) {
2541
+ return;
2319
2542
  }
2543
+ // <- Note: [🚲]
2544
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2545
+ Invalid Promptbook Version "${pipeline.bookVersion}"
2546
+
2547
+ ${block(pipelineIdentification)}
2548
+ `));
2549
+ }
2550
+ /**
2551
+ * Validates that `pipeline.parameters` is an array.
2552
+ *
2553
+ * @private internal utility of `validatePipeline`
2554
+ */
2555
+ function validatePipelineParametersCollection(pipeline, pipelineIdentification) {
2320
2556
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2321
- if (!Array.isArray(pipeline.tasks)) {
2322
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2323
- throw new ParseError(spaceTrim$1((block) => `
2324
- Pipeline is valid JSON but with wrong structure
2557
+ if (Array.isArray(pipeline.parameters)) {
2558
+ return;
2559
+ }
2560
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2561
+ throw new ParseError(spaceTrim$1((block) => `
2562
+ Pipeline is valid JSON but with wrong structure
2325
2563
 
2326
- \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2564
+ \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2327
2565
 
2328
- ${block(pipelineIdentification)}
2329
- `));
2566
+ ${block(pipelineIdentification)}
2567
+ `));
2568
+ }
2569
+ /**
2570
+ * Validates that `pipeline.tasks` is an array.
2571
+ *
2572
+ * @private internal utility of `validatePipeline`
2573
+ */
2574
+ function validatePipelineTasksCollection(pipeline, pipelineIdentification) {
2575
+ // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2576
+ if (Array.isArray(pipeline.tasks)) {
2577
+ return;
2330
2578
  }
2331
- /*
2332
- TODO: [🧠][🅾] Should be empty pipeline valid or not
2333
- // Note: Check that pipeline has some tasks
2334
- if (pipeline.tasks.length === 0) {
2335
- throw new PipelineLogicError(
2336
- spaceTrim(
2337
- (block) => `
2338
- Pipeline must have at least one task
2579
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2580
+ throw new ParseError(spaceTrim$1((block) => `
2581
+ Pipeline is valid JSON but with wrong structure
2339
2582
 
2340
- ${block(pipelineIdentification)}
2341
- `,
2342
- ),
2343
- );
2583
+ \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2584
+
2585
+ ${block(pipelineIdentification)}
2586
+ `));
2587
+ }
2588
+ /**
2589
+ * Validates that one parameter does not declare incompatible directions.
2590
+ *
2591
+ * @private internal utility of `validatePipeline`
2592
+ */
2593
+ function validateParameterDirection(parameter, pipelineIdentification) {
2594
+ if (!parameter.isInput || !parameter.isOutput) {
2595
+ return;
2344
2596
  }
2345
- */
2346
- // Note: Check each parameter individually
2347
- for (const parameter of pipeline.parameters) {
2348
- if (parameter.isInput && parameter.isOutput) {
2349
- throw new PipelineLogicError(spaceTrim$1((block) => `
2597
+ const parameterName = parameter.name;
2598
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2350
2599
 
2351
- Parameter \`{${parameter.name}}\` can not be both input and output
2600
+ Parameter \`{${parameterName}}\` can not be both input and output
2352
2601
 
2353
- ${block(pipelineIdentification)}
2354
- `));
2355
- }
2356
- // Note: Testing that parameter is either intermediate or output BUT not created and unused
2357
- if (!parameter.isInput &&
2358
- !parameter.isOutput &&
2359
- !pipeline.tasks.some((task) => task.dependentParameterNames.includes(parameter.name))) {
2360
- throw new PipelineLogicError(spaceTrim$1((block) => `
2361
- Parameter \`{${parameter.name}}\` is created but not used
2602
+ ${block(pipelineIdentification)}
2603
+ `));
2604
+ }
2605
+ /**
2606
+ * Validates that one intermediate parameter is actually consumed by at least one task.
2607
+ *
2608
+ * @private internal utility of `validatePipeline`
2609
+ */
2610
+ function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
2611
+ if (parameter.isInput || parameter.isOutput || isParameterUsedByAnyTask(parameter, pipeline.tasks)) {
2612
+ return;
2613
+ }
2614
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2615
+ Parameter \`{${parameter.name}}\` is created but not used
2362
2616
 
2363
- You can declare {${parameter.name}} as output parameter by adding in the header:
2364
- - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2617
+ You can declare {${parameter.name}} as output parameter by adding in the header:
2618
+ - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2365
2619
 
2366
- ${block(pipelineIdentification)}
2620
+ ${block(pipelineIdentification)}
2367
2621
 
2368
- `));
2369
- }
2370
- // Note: Testing that parameter is either input or result of some task
2371
- if (!parameter.isInput && !pipeline.tasks.some((task) => task.resultingParameterName === parameter.name)) {
2372
- throw new PipelineLogicError(spaceTrim$1((block) => `
2373
- Parameter \`{${parameter.name}}\` is declared but not defined
2622
+ `));
2623
+ }
2624
+ /**
2625
+ * Validates that one non-input parameter is produced by at least one task.
2626
+ *
2627
+ * @private internal utility of `validatePipeline`
2628
+ */
2629
+ function validateParameterDefinition(parameter, pipeline, pipelineIdentification) {
2630
+ if (parameter.isInput || isParameterDefinedByAnyTask(parameter, pipeline.tasks)) {
2631
+ return;
2632
+ }
2633
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2634
+ Parameter \`{${parameter.name}}\` is declared but not defined
2374
2635
 
2375
- You can do one of these:
2376
- 1) Remove declaration of \`{${parameter.name}}\`
2377
- 2) Add task that results in \`-> {${parameter.name}}\`
2636
+ You can do one of these:
2637
+ 1) Remove declaration of \`{${parameter.name}}\`
2638
+ 2) Add task that results in \`-> {${parameter.name}}\`
2378
2639
 
2379
- ${block(pipelineIdentification)}
2380
- `));
2381
- }
2640
+ ${block(pipelineIdentification)}
2641
+ `));
2642
+ }
2643
+ /**
2644
+ * Checks whether one parameter is consumed by at least one task dependency list.
2645
+ *
2646
+ * @private internal utility of `validatePipeline`
2647
+ */
2648
+ function isParameterUsedByAnyTask(parameter, tasks) {
2649
+ return tasks.some((task) => task.dependentParameterNames.includes(parameter.name));
2650
+ }
2651
+ /**
2652
+ * Checks whether one parameter is produced by at least one task.
2653
+ *
2654
+ * @private internal utility of `validatePipeline`
2655
+ */
2656
+ function isParameterDefinedByAnyTask(parameter, tasks) {
2657
+ return tasks.some((task) => task.resultingParameterName === parameter.name);
2658
+ }
2659
+ /**
2660
+ * Collects the parameter names that are already defined before task validation starts.
2661
+ *
2662
+ * @private internal utility of `validatePipeline`
2663
+ */
2664
+ function createInitiallyDefinedParameters(pipeline) {
2665
+ return new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2666
+ }
2667
+ /**
2668
+ * Validates one task result parameter declaration and marks it as defined.
2669
+ *
2670
+ * @private internal utility of `validatePipeline`
2671
+ */
2672
+ function validateTaskResultingParameter(task, definedParameters, pipelineIdentification) {
2673
+ if (definedParameters.has(task.resultingParameterName)) {
2674
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2675
+ Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2676
+
2677
+ ${block(pipelineIdentification)}
2678
+ `));
2382
2679
  }
2383
- // Note: All input parameters are defined - so that they can be used as result of some task
2384
- const definedParameters = new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2385
- // Note: Checking each task individually
2386
- for (const task of pipeline.tasks) {
2387
- if (definedParameters.has(task.resultingParameterName)) {
2388
- throw new PipelineLogicError(spaceTrim$1((block) => `
2389
- Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2680
+ if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2681
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2682
+ Parameter name {${task.resultingParameterName}} is reserved, please use different name
2390
2683
 
2391
- ${block(pipelineIdentification)}
2392
- `));
2393
- }
2394
- if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2395
- throw new PipelineLogicError(spaceTrim$1((block) => `
2396
- Parameter name {${task.resultingParameterName}} is reserved, please use different name
2684
+ ${block(pipelineIdentification)}
2685
+ `));
2686
+ }
2687
+ definedParameters.add(task.resultingParameterName);
2688
+ }
2689
+ /**
2690
+ * Validates joker parameters for one task.
2691
+ *
2692
+ * @private internal utility of `validatePipeline`
2693
+ */
2694
+ function validateTaskJokers(task, pipelineIdentification) {
2695
+ if (!hasTaskJokers(task)) {
2696
+ return;
2697
+ }
2698
+ validateTaskSupportsJokers(task, pipelineIdentification);
2699
+ validateTaskJokerDependencies(task, pipelineIdentification);
2700
+ }
2701
+ /**
2702
+ * Checks whether one task declares any joker parameters.
2703
+ *
2704
+ * @private internal utility of `validatePipeline`
2705
+ */
2706
+ function hasTaskJokers(task) {
2707
+ return !!task.jokerParameterNames && task.jokerParameterNames.length > 0;
2708
+ }
2709
+ /**
2710
+ * Validates that a task has the required supporting features when using jokers.
2711
+ *
2712
+ * @private internal utility of `validatePipeline`
2713
+ */
2714
+ function validateTaskSupportsJokers(task, pipelineIdentification) {
2715
+ if (task.format ||
2716
+ task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2717
+ return;
2718
+ }
2719
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2720
+ Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2397
2721
 
2398
- ${block(pipelineIdentification)}
2399
- `));
2722
+ ${block(pipelineIdentification)}
2723
+ `));
2724
+ }
2725
+ /**
2726
+ * Validates that every joker parameter is also listed among task dependencies.
2727
+ *
2728
+ * @private internal utility of `validatePipeline`
2729
+ */
2730
+ function validateTaskJokerDependencies(task, pipelineIdentification) {
2731
+ for (const joker of task.jokerParameterNames) {
2732
+ if (task.dependentParameterNames.includes(joker)) {
2733
+ continue;
2400
2734
  }
2401
- definedParameters.add(task.resultingParameterName);
2402
- if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
2403
- if (!task.format &&
2404
- !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2405
- throw new PipelineLogicError(spaceTrim$1((block) => `
2406
- Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2407
-
2408
- ${block(pipelineIdentification)}
2409
- `));
2410
- }
2411
- for (const joker of task.jokerParameterNames) {
2412
- if (!task.dependentParameterNames.includes(joker)) {
2413
- throw new PipelineLogicError(spaceTrim$1((block) => `
2414
- Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2735
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2736
+ Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2415
2737
 
2416
- ${block(pipelineIdentification)}
2417
- `));
2418
- }
2419
- }
2420
- }
2421
- if (task.expectations) {
2422
- for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2423
- if (min !== undefined && max !== undefined && min > max) {
2424
- throw new PipelineLogicError(spaceTrim$1((block) => `
2425
- Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2738
+ ${block(pipelineIdentification)}
2739
+ `));
2740
+ }
2741
+ }
2742
+ /**
2743
+ * Validates all expectation bounds configured on one task.
2744
+ *
2745
+ * @private internal utility of `validatePipeline`
2746
+ */
2747
+ function validateTaskExpectations(task, pipelineIdentification) {
2748
+ if (!task.expectations) {
2749
+ return;
2750
+ }
2751
+ for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2752
+ validateTaskExpectationRange(unit, min, max, pipelineIdentification);
2753
+ validateTaskExpectationMin(unit, min, pipelineIdentification);
2754
+ validateTaskExpectationMax(unit, max, pipelineIdentification);
2755
+ }
2756
+ }
2757
+ /**
2758
+ * Validates the minimum and maximum expectation ordering for one unit.
2759
+ *
2760
+ * @private internal utility of `validatePipeline`
2761
+ */
2762
+ function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
2763
+ if (min === undefined || max === undefined || min <= max) {
2764
+ return;
2765
+ }
2766
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2767
+ Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2426
2768
 
2427
- ${block(pipelineIdentification)}
2428
- `));
2429
- }
2430
- if (min !== undefined && min < 0) {
2431
- throw new PipelineLogicError(spaceTrim$1((block) => `
2432
- Min expectation of ${unit} must be zero or positive
2769
+ ${block(pipelineIdentification)}
2770
+ `));
2771
+ }
2772
+ /**
2773
+ * Validates the minimum expectation bound for one unit.
2774
+ *
2775
+ * @private internal utility of `validatePipeline`
2776
+ */
2777
+ function validateTaskExpectationMin(unit, min, pipelineIdentification) {
2778
+ if (min === undefined || min >= 0) {
2779
+ return;
2780
+ }
2781
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2782
+ Min expectation of ${unit} must be zero or positive
2433
2783
 
2434
- ${block(pipelineIdentification)}
2435
- `));
2436
- }
2437
- if (max !== undefined && max <= 0) {
2438
- throw new PipelineLogicError(spaceTrim$1((block) => `
2439
- Max expectation of ${unit} must be positive
2784
+ ${block(pipelineIdentification)}
2785
+ `));
2786
+ }
2787
+ /**
2788
+ * Validates the maximum expectation bound for one unit.
2789
+ *
2790
+ * @private internal utility of `validatePipeline`
2791
+ */
2792
+ function validateTaskExpectationMax(unit, max, pipelineIdentification) {
2793
+ if (max === undefined || max > 0) {
2794
+ return;
2795
+ }
2796
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2797
+ Max expectation of ${unit} must be positive
2440
2798
 
2441
- ${block(pipelineIdentification)}
2442
- `));
2443
- }
2444
- }
2445
- }
2799
+ ${block(pipelineIdentification)}
2800
+ `));
2801
+ }
2802
+ /**
2803
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
2804
+ *
2805
+ * @private internal utility of `validatePipeline`
2806
+ */
2807
+ function createInitialDependencyResolutionState(pipeline) {
2808
+ return {
2809
+ resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
2810
+ unresolvedTasks: [...pipeline.tasks],
2811
+ };
2812
+ }
2813
+ /**
2814
+ * Checks whether dependency resolution still has tasks left to process.
2815
+ *
2816
+ * @private internal utility of `validatePipeline`
2817
+ */
2818
+ function hasUnresolvedTasks({ unresolvedTasks }) {
2819
+ return unresolvedTasks.length > 0;
2820
+ }
2821
+ /**
2822
+ * Resolves the next batch of currently satisfiable tasks.
2823
+ *
2824
+ * @private internal utility of `validatePipeline`
2825
+ */
2826
+ function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
2827
+ const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
2828
+ if (currentlyResolvedTasks.length === 0) {
2829
+ throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
2446
2830
  }
2447
- // Note: Detect circular dependencies
2448
- let resovedParameters = pipeline.parameters
2831
+ return {
2832
+ resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
2833
+ unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
2834
+ };
2835
+ }
2836
+ /**
2837
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
2838
+ *
2839
+ * @private internal utility of `validatePipeline`
2840
+ */
2841
+ function createInitiallyResolvedParameterNames(pipeline) {
2842
+ let resolvedParameterNames = pipeline.parameters
2449
2843
  .filter(({ isInput }) => isInput)
2450
2844
  .map(({ name }) => name);
2451
- // Note: All reserved parameters are resolved
2452
2845
  for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
2453
- resovedParameters = [...resovedParameters, reservedParameterName];
2846
+ resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
2454
2847
  }
2455
- let unresovedTasks = [...pipeline.tasks];
2456
- let loopLimit = LOOP_LIMIT;
2457
- while (unresovedTasks.length > 0) {
2458
- if (loopLimit-- < 0) {
2459
- // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2460
- throw new UnexpectedError(spaceTrim$1((block) => `
2461
- Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2848
+ return resolvedParameterNames;
2849
+ }
2850
+ /**
2851
+ * Adds newly resolved task outputs to the resolved parameter list.
2852
+ *
2853
+ * @private internal utility of `validatePipeline`
2854
+ */
2855
+ function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
2856
+ return [
2857
+ ...resolvedParameterNames,
2858
+ ...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
2859
+ ];
2860
+ }
2861
+ /**
2862
+ * Selects tasks whose dependencies are already resolved.
2863
+ *
2864
+ * @private internal utility of `validatePipeline`
2865
+ */
2866
+ function getCurrentlyResolvedTasks(unresolvedTasks, resolvedParameterNames) {
2867
+ return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
2868
+ }
2869
+ /**
2870
+ * Creates the unexpected loop-limit error for dependency resolution.
2871
+ *
2872
+ * @private internal utility of `validatePipeline`
2873
+ */
2874
+ function createDependencyResolutionLoopLimitError(pipelineIdentification) {
2875
+ // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2876
+ return new UnexpectedError(spaceTrim$1((block) => `
2877
+ Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2462
2878
 
2463
- ${block(pipelineIdentification)}
2464
- `));
2465
- }
2466
- const currentlyResovedTasks = unresovedTasks.filter((task) => task.dependentParameterNames.every((name) => resovedParameters.includes(name)));
2467
- if (currentlyResovedTasks.length === 0) {
2468
- throw new PipelineLogicError(
2469
- // TODO: [🐎] DRY
2470
- spaceTrim$1((block) => `
2879
+ ${block(pipelineIdentification)}
2880
+ `));
2881
+ }
2882
+ /**
2883
+ * Creates the detailed error for unresolved or circular task dependencies.
2884
+ *
2885
+ * @private internal utility of `validatePipeline`
2886
+ */
2887
+ function createUnresolvedTasksError(unresolvedTasks, resolvedParameterNames, pipelineIdentification) {
2888
+ return new PipelineLogicError(
2889
+ // TODO: [🐎] DRY
2890
+ spaceTrim$1((block) => `
2471
2891
 
2472
- Can not resolve some parameters:
2473
- Either you are using a parameter that is not defined, or there are some circular dependencies.
2892
+ Can not resolve some parameters:
2893
+ Either you are using a parameter that is not defined, or there are some circular dependencies.
2474
2894
 
2475
- ${block(pipelineIdentification)}
2895
+ ${block(pipelineIdentification)}
2476
2896
 
2477
- **Can not resolve:**
2478
- ${block(unresovedTasks
2479
- .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2480
- .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2481
- .join(' and ')}`)
2482
- .join('\n'))}
2897
+ **Can not resolve:**
2898
+ ${block(unresolvedTasks
2899
+ .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2900
+ .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2901
+ .join(' and ')}`)
2902
+ .join('\n'))}
2483
2903
 
2484
- **Resolved:**
2485
- ${block(resovedParameters
2486
- .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2487
- .map((name) => `- Parameter \`{${name}}\``)
2488
- .join('\n'))}
2904
+ **Resolved:**
2905
+ ${block(resolvedParameterNames
2906
+ .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2907
+ .map((name) => `- Parameter \`{${name}}\``)
2908
+ .join('\n'))}
2489
2909
 
2490
2910
 
2491
- **Reserved (which are available):**
2492
- ${block(resovedParameters
2493
- .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2494
- .map((name) => `- Parameter \`{${name}}\``)
2495
- .join('\n'))}
2911
+ **Reserved (which are available):**
2912
+ ${block(resolvedParameterNames
2913
+ .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2914
+ .map((name) => `- Parameter \`{${name}}\``)
2915
+ .join('\n'))}
2496
2916
 
2497
2917
 
2498
- `));
2499
- }
2500
- resovedParameters = [
2501
- ...resovedParameters,
2502
- ...currentlyResovedTasks.map(({ resultingParameterName }) => resultingParameterName),
2503
- ];
2504
- unresovedTasks = unresovedTasks.filter((task) => !currentlyResovedTasks.includes(task));
2505
- }
2506
- // Note: Check that formfactor is corresponding to the pipeline interface
2507
- // TODO: !!6 Implement this
2508
- // pipeline.formfactorName
2918
+ `));
2509
2919
  }
2510
2920
  /**
2511
2921
  * TODO: [🧞‍♀️] Do not allow joker + foreach
@@ -3227,170 +3637,319 @@ function assertsTaskSuccessful(executionResult) {
3227
3637
  // TODO: [🧠] Can this return type be better typed than void
3228
3638
 
3229
3639
  /**
3230
- * Helper to create a new task
3640
+ * Resolves the short task summary shown in the UI.
3231
3641
  *
3232
- * @private internal helper function
3642
+ * @private internal helper function of `ExecutionTask`
3233
3643
  */
3234
- function createTask(options) {
3235
- const { taskType, taskProcessCallback } = options;
3236
- let { title } = options;
3237
- // TODO: [🐙] DRY
3238
- const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3239
- let status = 'RUNNING';
3240
- const createdAt = new Date();
3241
- let updatedAt = createdAt;
3242
- const errors = [];
3243
- const warnings = [];
3244
- const llmCalls = [];
3245
- let currentValue = {};
3246
- let customTldr = null;
3247
- const partialResultSubject = new Subject();
3248
- // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3249
- const finalResultPromise = /* not await */ taskProcessCallback((newOngoingResult) => {
3250
- if (newOngoingResult.title) {
3251
- title = newOngoingResult.title;
3252
- }
3253
- updatedAt = new Date();
3254
- Object.assign(currentValue, newOngoingResult);
3255
- // <- TODO: assign deep
3256
- partialResultSubject.next(newOngoingResult);
3257
- }, (tldrInfo) => {
3258
- customTldr = tldrInfo;
3259
- updatedAt = new Date();
3260
- }, (llmCall) => {
3261
- llmCalls.push(llmCall);
3262
- updatedAt = new Date();
3263
- });
3264
- finalResultPromise
3265
- .catch((error) => {
3266
- errors.push(error);
3267
- partialResultSubject.error(error);
3268
- })
3269
- .then((executionResult) => {
3270
- if (executionResult) {
3271
- try {
3272
- updatedAt = new Date();
3273
- errors.push(...executionResult.errors);
3274
- warnings.push(...executionResult.warnings);
3275
- // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3276
- // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3277
- // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3278
- // And delete `ExecutionTask.currentValue.preparedPipeline`
3279
- assertsTaskSuccessful(executionResult);
3280
- status = 'FINISHED';
3281
- currentValue = jsonStringsToJsons(executionResult);
3282
- // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3283
- partialResultSubject.next(executionResult);
3284
- }
3285
- catch (error) {
3286
- assertsError(error);
3287
- status = 'ERROR';
3288
- errors.push(error);
3289
- partialResultSubject.error(error);
3290
- }
3291
- }
3292
- partialResultSubject.complete();
3293
- });
3294
- async function asPromise(options) {
3295
- const { isCrashedOnError = true } = options || {};
3296
- const finalResult = await finalResultPromise;
3297
- if (isCrashedOnError) {
3298
- assertsTaskSuccessful(finalResult);
3299
- }
3300
- return finalResult;
3644
+ function resolveTaskTldr(options) {
3645
+ const { customTldr } = options;
3646
+ if (customTldr) {
3647
+ return customTldr;
3301
3648
  }
3302
3649
  return {
3303
- taskType,
3304
- taskId,
3305
- get promptbookVersion() {
3306
- return PROMPTBOOK_ENGINE_VERSION;
3650
+ percent: resolveTaskPercent(options),
3651
+ message: `${resolveTaskMessage(options)} (!!!fallback)`,
3652
+ };
3653
+ }
3654
+ /**
3655
+ * Resolves the best progress percentage for the current task state.
3656
+ *
3657
+ * @private internal helper function of `ExecutionTask`
3658
+ */
3659
+ function resolveTaskPercent(options) {
3660
+ const explicitPercent = getExplicitTaskPercent(options.currentValue);
3661
+ if (typeof explicitPercent === 'number') {
3662
+ return normalizeTaskPercent(explicitPercent);
3663
+ }
3664
+ return normalizeTaskPercent(calculateSimulatedTaskPercent(options));
3665
+ }
3666
+ /**
3667
+ * Picks a directly reported progress percentage from the task result snapshot.
3668
+ *
3669
+ * @private internal helper function of `ExecutionTask`
3670
+ */
3671
+ function getExplicitTaskPercent(currentValue) {
3672
+ var _a, _b, _c, _d, _e, _f;
3673
+ return ((_f = (_d = (_b = (_a = currentValue === null || currentValue === void 0 ? void 0 : currentValue.tldr) === null || _a === void 0 ? void 0 : _a.percent) !== null && _b !== void 0 ? _b : (_c = currentValue === null || currentValue === void 0 ? void 0 : currentValue.usage) === null || _c === void 0 ? void 0 : _c.percent) !== null && _d !== void 0 ? _d : (_e = currentValue === null || currentValue === void 0 ? void 0 : currentValue.progress) === null || _e === void 0 ? void 0 : _e.percent) !== null && _f !== void 0 ? _f : currentValue === null || currentValue === void 0 ? void 0 : currentValue.percent);
3674
+ }
3675
+ /**
3676
+ * Simulates progress when the task result does not expose an explicit percentage.
3677
+ *
3678
+ * @private internal helper function of `ExecutionTask`
3679
+ */
3680
+ function calculateSimulatedTaskPercent(options) {
3681
+ const { currentValue, status, createdAt } = options;
3682
+ const elapsedMs = new Date().getTime() - createdAt.getTime();
3683
+ const timeProgress = Math.min(elapsedMs / DEFAULT_TASK_SIMULATED_DURATION_MS, 1);
3684
+ const { subtaskCount, completedSubtasks } = summarizeTaskSubtasks(currentValue);
3685
+ if (status === 'FINISHED') {
3686
+ return 1;
3687
+ }
3688
+ if (status === 'ERROR') {
3689
+ return 0;
3690
+ }
3691
+ return Math.min(completedSubtasks / subtaskCount + (1 / subtaskCount) * timeProgress, 1);
3692
+ }
3693
+ /**
3694
+ * Counts total and completed subtasks used by the fallback progress simulation.
3695
+ *
3696
+ * @private internal helper function of `ExecutionTask`
3697
+ */
3698
+ function summarizeTaskSubtasks(currentValue) {
3699
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks)) {
3700
+ return { subtaskCount: 1, completedSubtasks: 0 };
3701
+ }
3702
+ return {
3703
+ subtaskCount: currentValue.subtasks.length || 1,
3704
+ completedSubtasks: currentValue.subtasks.filter(isTaskSubtaskCompleted).length,
3705
+ };
3706
+ }
3707
+ /**
3708
+ * Tells whether a task subtask is already finished.
3709
+ *
3710
+ * @private internal helper function of `ExecutionTask`
3711
+ */
3712
+ function isTaskSubtaskCompleted(subtask) {
3713
+ return subtask.done || subtask.completed || false;
3714
+ }
3715
+ /**
3716
+ * Normalizes a progress percentage into the expected `0..1` range.
3717
+ *
3718
+ * @private internal helper function of `ExecutionTask`
3719
+ */
3720
+ function normalizeTaskPercent(percentRaw) {
3721
+ let percent = Number(percentRaw) || 0;
3722
+ if (percent < 0) {
3723
+ percent = 0;
3724
+ }
3725
+ if (percent > 1) {
3726
+ percent = 1;
3727
+ }
3728
+ return percent;
3729
+ }
3730
+ /**
3731
+ * Resolves the best human-readable status message for the current task state.
3732
+ *
3733
+ * @private internal helper function of `ExecutionTask`
3734
+ */
3735
+ function resolveTaskMessage(options) {
3736
+ return (getCurrentValueMessage(options.currentValue) ||
3737
+ getCurrentSubtaskMessage(options.currentValue) ||
3738
+ getLatestIssueMessage(options.errors, 'Error') ||
3739
+ getLatestIssueMessage(options.warnings, 'Warning') ||
3740
+ getStatusMessage(options.status));
3741
+ }
3742
+ /**
3743
+ * Picks a message already reported by the current task result snapshot.
3744
+ *
3745
+ * @private internal helper function of `ExecutionTask`
3746
+ */
3747
+ function getCurrentValueMessage(currentValue) {
3748
+ var _a, _b, _c, _d;
3749
+ return (_d = (_c = (_b = (_a = currentValue === null || currentValue === void 0 ? void 0 : currentValue.tldr) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : currentValue === null || currentValue === void 0 ? void 0 : currentValue.message) !== null && _c !== void 0 ? _c : currentValue === null || currentValue === void 0 ? void 0 : currentValue.summary) !== null && _d !== void 0 ? _d : currentValue === null || currentValue === void 0 ? void 0 : currentValue.statusMessage;
3750
+ }
3751
+ /**
3752
+ * Builds a fallback message from the first unfinished subtask title.
3753
+ *
3754
+ * @private internal helper function of `ExecutionTask`
3755
+ */
3756
+ function getCurrentSubtaskMessage(currentValue) {
3757
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks) || currentValue.subtasks.length === 0) {
3758
+ return undefined;
3759
+ }
3760
+ const currentSubtask = currentValue.subtasks.find((subtask) => !isTaskSubtaskCompleted(subtask));
3761
+ if (!(currentSubtask === null || currentSubtask === void 0 ? void 0 : currentSubtask.title)) {
3762
+ return undefined;
3763
+ }
3764
+ return `Working on ${currentSubtask.title}`;
3765
+ }
3766
+ /**
3767
+ * Picks the latest error or warning message, with the legacy generic fallback label.
3768
+ *
3769
+ * @private internal helper function of `ExecutionTask`
3770
+ */
3771
+ function getLatestIssueMessage(issues, fallbackMessage) {
3772
+ if (issues.length === 0) {
3773
+ return undefined;
3774
+ }
3775
+ return issues[issues.length - 1].message || fallbackMessage;
3776
+ }
3777
+ /**
3778
+ * Builds the final status-based fallback message.
3779
+ *
3780
+ * @private internal helper function of `ExecutionTask`
3781
+ */
3782
+ function getStatusMessage(status) {
3783
+ if (status === 'FINISHED') {
3784
+ return 'Finished';
3785
+ }
3786
+ if (status === 'ERROR') {
3787
+ return 'Error';
3788
+ }
3789
+ return 'Running';
3790
+ }
3791
+
3792
+ /**
3793
+ * Creates the initial mutable state for a task.
3794
+ *
3795
+ * @private internal helper function
3796
+ */
3797
+ function createTaskState(title, createdAt) {
3798
+ return {
3799
+ title,
3800
+ status: 'RUNNING',
3801
+ updatedAt: createdAt,
3802
+ errors: [],
3803
+ warnings: [],
3804
+ llmCalls: [],
3805
+ currentValue: {},
3806
+ customTldr: null,
3807
+ };
3808
+ }
3809
+ /**
3810
+ * Creates the partial-result updater passed into the task process callback.
3811
+ *
3812
+ * @private internal helper function
3813
+ */
3814
+ function createOngoingResultUpdater(taskState, partialResultSubject) {
3815
+ return (newOngoingResult) => {
3816
+ if (newOngoingResult.title) {
3817
+ taskState.title = newOngoingResult.title;
3818
+ }
3819
+ taskState.updatedAt = new Date();
3820
+ Object.assign(taskState.currentValue, newOngoingResult);
3821
+ // <- TODO: assign deep
3822
+ partialResultSubject.next(newOngoingResult);
3823
+ };
3824
+ }
3825
+ /**
3826
+ * Creates the custom-TLDR updater passed into the task process callback.
3827
+ *
3828
+ * @private internal helper function
3829
+ */
3830
+ function createTldrUpdater(taskState) {
3831
+ return (tldrInfo) => {
3832
+ taskState.customTldr = tldrInfo;
3833
+ taskState.updatedAt = new Date();
3834
+ };
3835
+ }
3836
+ /**
3837
+ * Creates the LLM call logger passed into the task process callback.
3838
+ *
3839
+ * @private internal helper function
3840
+ */
3841
+ function createLlmCallLogger(taskState) {
3842
+ return (llmCall) => {
3843
+ taskState.llmCalls.push(llmCall);
3844
+ taskState.updatedAt = new Date();
3845
+ };
3846
+ }
3847
+ /**
3848
+ * Wires the task promise into the observable/error lifecycle.
3849
+ *
3850
+ * @private internal helper function
3851
+ */
3852
+ function settleTaskPromise(finalResultPromise, taskState, partialResultSubject) {
3853
+ finalResultPromise
3854
+ .catch((error) => {
3855
+ taskState.errors.push(error);
3856
+ partialResultSubject.error(error);
3857
+ })
3858
+ .then((executionResult) => {
3859
+ if (executionResult) {
3860
+ try {
3861
+ finalizeTaskResult(executionResult, taskState, partialResultSubject);
3862
+ }
3863
+ catch (error) {
3864
+ failTaskResult(error, taskState, partialResultSubject);
3865
+ }
3866
+ }
3867
+ partialResultSubject.complete();
3868
+ });
3869
+ }
3870
+ /**
3871
+ * Applies the final successful task result into the mutable task state.
3872
+ *
3873
+ * @private internal helper function
3874
+ */
3875
+ function finalizeTaskResult(executionResult, taskState, partialResultSubject) {
3876
+ taskState.updatedAt = new Date();
3877
+ taskState.errors.push(...executionResult.errors);
3878
+ taskState.warnings.push(...executionResult.warnings);
3879
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3880
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3881
+ // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3882
+ // And delete `ExecutionTask.currentValue.preparedPipeline`
3883
+ assertsTaskSuccessful(executionResult);
3884
+ taskState.status = 'FINISHED';
3885
+ taskState.currentValue = jsonStringsToJsons(executionResult);
3886
+ // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3887
+ partialResultSubject.next(executionResult);
3888
+ }
3889
+ /**
3890
+ * Records a final-result failure after the task promise itself resolved.
3891
+ *
3892
+ * @private internal helper function
3893
+ */
3894
+ function failTaskResult(error, taskState, partialResultSubject) {
3895
+ assertsError(error);
3896
+ taskState.status = 'ERROR';
3897
+ taskState.errors.push(error);
3898
+ partialResultSubject.error(error);
3899
+ }
3900
+ /**
3901
+ * Helper to create a new task
3902
+ *
3903
+ * @private internal helper function
3904
+ */
3905
+ function createTask(options) {
3906
+ const { taskType, title, taskProcessCallback } = options;
3907
+ // TODO: [🐙] DRY
3908
+ const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3909
+ const createdAt = new Date();
3910
+ const taskState = createTaskState(title, createdAt);
3911
+ const partialResultSubject = new Subject();
3912
+ // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3913
+ const finalResultPromise = /* not await */ taskProcessCallback(createOngoingResultUpdater(taskState, partialResultSubject), createTldrUpdater(taskState), createLlmCallLogger(taskState));
3914
+ settleTaskPromise(finalResultPromise, taskState, partialResultSubject);
3915
+ async function asPromise(options) {
3916
+ const { isCrashedOnError = true } = options || {};
3917
+ const finalResult = await finalResultPromise;
3918
+ if (isCrashedOnError) {
3919
+ assertsTaskSuccessful(finalResult);
3920
+ }
3921
+ return finalResult;
3922
+ }
3923
+ return {
3924
+ taskType,
3925
+ taskId,
3926
+ get promptbookVersion() {
3927
+ return PROMPTBOOK_ENGINE_VERSION;
3307
3928
  },
3308
3929
  get title() {
3309
- return title;
3930
+ return taskState.title;
3310
3931
  // <- Note: [1] These must be getters to allow changing the value in the future
3311
3932
  },
3312
3933
  get status() {
3313
- return status;
3934
+ return taskState.status;
3314
3935
  // <- Note: [1] --||--
3315
3936
  },
3316
3937
  get tldr() {
3317
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3318
- // Use custom tldr if available
3319
- if (customTldr) {
3320
- return customTldr;
3321
- }
3322
- // Fallback to default implementation
3323
- const cv = currentValue;
3324
- // If explicit percent is provided, use it
3325
- let percentRaw = (_f = (_d = (_b = (_a = cv === null || cv === void 0 ? void 0 : cv.tldr) === null || _a === void 0 ? void 0 : _a.percent) !== null && _b !== void 0 ? _b : (_c = cv === null || cv === void 0 ? void 0 : cv.usage) === null || _c === void 0 ? void 0 : _c.percent) !== null && _d !== void 0 ? _d : (_e = cv === null || cv === void 0 ? void 0 : cv.progress) === null || _e === void 0 ? void 0 : _e.percent) !== null && _f !== void 0 ? _f : cv === null || cv === void 0 ? void 0 : cv.percent;
3326
- // Simulate progress if not provided
3327
- if (typeof percentRaw !== 'number') {
3328
- // Simulate progress: evenly split across subtasks, based on elapsed time
3329
- const now = new Date();
3330
- const elapsedMs = now.getTime() - createdAt.getTime();
3331
- const totalMs = DEFAULT_TASK_SIMULATED_DURATION_MS;
3332
- // If subtasks are defined, split progress evenly
3333
- const subtaskCount = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) ? cv.subtasks.length : 1;
3334
- const completedSubtasks = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks)
3335
- ? cv.subtasks.filter((s) => s.done || s.completed).length
3336
- : 0;
3337
- // Progress from completed subtasks
3338
- const subtaskProgress = subtaskCount > 0 ? completedSubtasks / subtaskCount : 0;
3339
- // Progress from elapsed time for current subtask
3340
- const timeProgress = Math.min(elapsedMs / totalMs, 1);
3341
- // Combine: completed subtasks + time progress for current subtask
3342
- percentRaw = Math.min(subtaskProgress + (1 / subtaskCount) * timeProgress, 1);
3343
- if (status === 'FINISHED')
3344
- percentRaw = 1;
3345
- if (status === 'ERROR')
3346
- percentRaw = 0;
3347
- }
3348
- // Clamp to [0,1]
3349
- let percent = Number(percentRaw) || 0;
3350
- if (percent < 0)
3351
- percent = 0;
3352
- if (percent > 1)
3353
- percent = 1;
3354
- // Build a short message: prefer explicit tldr.message, then common summary/message fields, then errors/warnings, then status
3355
- const messageFromResult = (_k = (_j = (_h = (_g = cv === null || cv === void 0 ? void 0 : cv.tldr) === null || _g === void 0 ? void 0 : _g.message) !== null && _h !== void 0 ? _h : cv === null || cv === void 0 ? void 0 : cv.message) !== null && _j !== void 0 ? _j : cv === null || cv === void 0 ? void 0 : cv.summary) !== null && _k !== void 0 ? _k : cv === null || cv === void 0 ? void 0 : cv.statusMessage;
3356
- let message = messageFromResult;
3357
- if (!message) {
3358
- // If subtasks, show current subtask
3359
- if (Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) && cv.subtasks.length > 0) {
3360
- const current = cv.subtasks.find((s) => !s.done && !s.completed);
3361
- if (current && current.title) {
3362
- message = `Working on ${current.title}`;
3363
- }
3364
- }
3365
- if (!message) {
3366
- if (errors.length) {
3367
- message = errors[errors.length - 1].message || 'Error';
3368
- }
3369
- else if (warnings.length) {
3370
- message = warnings[warnings.length - 1].message || 'Warning';
3371
- }
3372
- else if (status === 'FINISHED') {
3373
- message = 'Finished';
3374
- }
3375
- else if (status === 'ERROR') {
3376
- message = 'Error';
3377
- }
3378
- else {
3379
- message = 'Running';
3380
- }
3381
- }
3382
- }
3383
- return {
3384
- percent: percent,
3385
- message: message + ' (!!!fallback)',
3386
- };
3938
+ return resolveTaskTldr({
3939
+ customTldr: taskState.customTldr,
3940
+ currentValue: taskState.currentValue,
3941
+ status: taskState.status,
3942
+ createdAt,
3943
+ errors: taskState.errors,
3944
+ warnings: taskState.warnings,
3945
+ });
3387
3946
  },
3388
3947
  get createdAt() {
3389
3948
  return createdAt;
3390
3949
  // <- Note: [1] --||--
3391
3950
  },
3392
3951
  get updatedAt() {
3393
- return updatedAt;
3952
+ return taskState.updatedAt;
3394
3953
  // <- Note: [1] --||--
3395
3954
  },
3396
3955
  asPromise,
@@ -3398,19 +3957,19 @@ function createTask(options) {
3398
3957
  return partialResultSubject.asObservable();
3399
3958
  },
3400
3959
  get errors() {
3401
- return errors;
3960
+ return taskState.errors;
3402
3961
  // <- Note: [1] --||--
3403
3962
  },
3404
3963
  get warnings() {
3405
- return warnings;
3964
+ return taskState.warnings;
3406
3965
  // <- Note: [1] --||--
3407
3966
  },
3408
3967
  get llmCalls() {
3409
- return [...llmCalls, { foo: '!!! bar' }];
3968
+ return [...taskState.llmCalls, { foo: '!!! bar' }];
3410
3969
  // <- Note: [1] --||--
3411
3970
  },
3412
3971
  get currentValue() {
3413
- return currentValue;
3972
+ return taskState.currentValue;
3414
3973
  // <- Note: [1] --||--
3415
3974
  },
3416
3975
  };
@@ -4724,231 +5283,296 @@ const promptbookFetch = async (urlOrRequest, init) => {
4724
5283
  * @public exported from `@promptbook/core`
4725
5284
  */
4726
5285
  async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
4727
- var _a;
4728
- const { fetch = promptbookFetch } = tools;
4729
5286
  const { knowledgeSourceContent } = knowledgeSource;
4730
- let { name } = knowledgeSource;
4731
- const { rootDirname = null,
4732
- // <- TODO: process.cwd() if running in Node.js
4733
- isVerbose = DEFAULT_IS_VERBOSE, } = options || {};
4734
- if (!name) {
4735
- name = knowledgeSourceContentToName(knowledgeSourceContent);
4736
- }
5287
+ const name = knowledgeSource.name || knowledgeSourceContentToName(knowledgeSourceContent);
5288
+ const { rootDirname = null, isVerbose = DEFAULT_IS_VERBOSE } = options || {};
4737
5289
  if (isValidUrl(knowledgeSourceContent)) {
4738
- const url = knowledgeSourceContent;
4739
- if (isVerbose) {
4740
- console.info(`📄 [1] "${name}" is available at "${url}"`);
4741
- }
4742
- const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
4743
- const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
4744
- if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
4745
- if (isVerbose) {
4746
- console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
4747
- }
4748
- return {
4749
- source: name,
4750
- filename: null,
4751
- url,
4752
- mimeType,
4753
- /*
4754
- TODO: [🥽]
4755
- > async asBlob() {
4756
- > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
4757
- > const content = await response.blob();
4758
- > return content;
4759
- > },
4760
- */
4761
- async asJson() {
4762
- // TODO: [👨🏻‍🤝‍👨🏻]
4763
- const content = await response.json();
4764
- return content;
4765
- },
4766
- async asText() {
4767
- // TODO: [👨🏻‍🤝‍👨🏻]
4768
- const content = await response.text();
4769
- return content;
4770
- },
4771
- };
4772
- }
4773
- const basename = url.split('/').pop() || titleToName(url);
4774
- const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
4775
- // <- TODO: [🥬] Encapsulate sha256 to some private utility function
4776
- const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
4777
- const filepath = join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
4778
- // Note: Try to create cache directory, but don't fail if filesystem has issues
4779
- try {
4780
- await tools.fs.mkdir(dirname(join(rootDirname, filepath)), { recursive: true });
4781
- }
4782
- catch (error) {
4783
- if (isVerbose) {
4784
- console.info(`📄 [3] "${name}" error creating cache directory`);
4785
- }
4786
- // Note: If we can't create cache directory, we'll handle it when trying to write the file
4787
- // This handles read-only filesystems, permission issues, and missing parent directories
4788
- if (error instanceof Error &&
4789
- (error.message.includes('EROFS') ||
4790
- error.message.includes('read-only') ||
4791
- error.message.includes('EACCES') ||
4792
- error.message.includes('EPERM') ||
4793
- error.message.includes('ENOENT'))) ;
4794
- else {
4795
- // Re-throw other unexpected errors
4796
- throw error;
4797
- }
4798
- }
4799
- const fileContent = Buffer.from(await response.arrayBuffer());
4800
- if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
4801
- throw new LimitReachedError(`File is too large (${Math.round(fileContent.length / 1024 / 1024)}MB). Maximum allowed size is ${Math.round(DEFAULT_MAX_FILE_SIZE / 1024 / 1024)}MB.`);
4802
- }
4803
- // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
4804
- try {
4805
- await tools.fs.writeFile(join(rootDirname, filepath), fileContent);
4806
- }
4807
- catch (error) {
4808
- if (isVerbose) {
4809
- console.info(`📄 [4] "${name}" error writing cache file`);
4810
- }
4811
- // Note: If we can't write to cache, we'll process the file directly from memory
4812
- // This handles read-only filesystems like Vercel
4813
- if (error instanceof Error &&
4814
- (error.message.includes('EROFS') ||
4815
- error.message.includes('read-only') ||
4816
- error.message.includes('EACCES') ||
4817
- error.message.includes('EPERM') ||
4818
- error.message.includes('ENOENT'))) {
4819
- // Return a handler that works directly with the downloaded content
4820
- return {
4821
- source: name,
4822
- filename: null,
4823
- url,
4824
- mimeType,
4825
- async asJson() {
4826
- return JSON.parse(fileContent.toString('utf-8'));
4827
- },
4828
- async asText() {
4829
- return fileContent.toString('utf-8');
4830
- },
4831
- };
4832
- }
4833
- else {
4834
- // Re-throw other unexpected errors
4835
- throw error;
4836
- }
4837
- }
4838
- // TODO: [💵] Check the file security
4839
- // TODO: [🧹][🧠] Delete the file after the scraping is done
4840
- if (isVerbose) {
4841
- console.info(`📄 [5] "${name}" cached at "${join(rootDirname, filepath)}"`);
4842
- }
4843
- return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
4844
- ...options,
4845
- rootDirname,
4846
- });
5290
+ return makeUrlKnowledgeSourceHandler(knowledgeSourceContent, name, tools, options, isVerbose);
4847
5291
  }
4848
- else if (isValidFilePath(knowledgeSourceContent)) {
4849
- if (tools.fs === undefined) {
4850
- throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
4851
- // <- TODO: [🧠] What is the best error type here`
4852
- }
4853
- if (rootDirname === null) {
4854
- throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
4855
- // <- TODO: [🧠] What is the best error type here`
4856
- }
4857
- const filename = isAbsolute(knowledgeSourceContent)
4858
- ? knowledgeSourceContent
4859
- : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
4860
- if (isVerbose) {
4861
- console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
4862
- }
4863
- const fileExtension = getFileExtension(filename);
4864
- const mimeType = extensionToMimeType(fileExtension || '');
4865
- if (!(await isFileExisting(filename, tools.fs))) {
4866
- throw new NotFoundError(spaceTrim$1((block) => `
4867
- Can not make source handler for file which does not exist:
4868
-
4869
- File:
4870
- ${block(knowledgeSourceContent)}
4871
-
4872
- Full file path:
4873
- ${block(filename)}
4874
- `));
4875
- }
4876
- // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
4877
- return {
4878
- source: name,
4879
- filename,
4880
- url: null,
4881
- mimeType,
4882
- /*
4883
- TODO: [🥽]
4884
- > async asBlob() {
4885
- > const content = await tools.fs!.readFile(filename);
4886
- > return new Blob(
4887
- > [
4888
- > content,
4889
- > // <- TODO: [🥽] This is NOT tested, test it
4890
- > ],
4891
- > { type: mimeType },
4892
- > );
4893
- > },
4894
- */
4895
- async asJson() {
4896
- return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
4897
- },
4898
- async asText() {
4899
- return await tools.fs.readFile(filename, 'utf-8');
4900
- },
4901
- };
5292
+ if (isValidFilePath(knowledgeSourceContent)) {
5293
+ return makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose);
4902
5294
  }
4903
- else {
4904
- if (isVerbose) {
4905
- console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
4906
- console.info('---');
4907
- console.info(knowledgeSourceContent);
4908
- console.info('---');
4909
- }
4910
- return {
4911
- source: name,
4912
- filename: null,
4913
- url: null,
4914
- mimeType: 'text/markdown',
4915
- asText() {
4916
- return knowledgeSource.knowledgeSourceContent;
4917
- },
4918
- asJson() {
4919
- throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
4920
- },
4921
- /*
4922
- TODO: [🥽]
4923
- > asBlob() {
4924
- > throw new UnexpectedError(
4925
- > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
4926
- > );
4927
- > },
4928
- */
4929
- };
5295
+ return makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose);
5296
+ }
5297
+ /**
5298
+ * Creates a source handler for URL-based knowledge.
5299
+ *
5300
+ * @private internal utility of `makeKnowledgeSourceHandler`
5301
+ */
5302
+ async function makeUrlKnowledgeSourceHandler(url, name, tools, options, isVerbose) {
5303
+ var _a;
5304
+ const { fetch = promptbookFetch } = tools;
5305
+ if (isVerbose) {
5306
+ console.info(`📄 [1] "${name}" is available at "${url}"`);
5307
+ }
5308
+ const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
5309
+ const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
5310
+ if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
5311
+ return makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose);
4930
5312
  }
5313
+ return cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose);
4931
5314
  }
4932
-
4933
5315
  /**
4934
- * Prepares the knowledge pieces
5316
+ * Creates a source handler that reads directly from a fetched response.
4935
5317
  *
4936
- * @see https://github.com/webgptorg/promptbook/discussions/41
5318
+ * @private internal utility of `makeKnowledgeSourceHandler`
5319
+ */
5320
+ function makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose) {
5321
+ if (isVerbose) {
5322
+ console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
5323
+ }
5324
+ return {
5325
+ source: name,
5326
+ filename: null,
5327
+ url,
5328
+ mimeType,
5329
+ /*
5330
+ TODO: [🥽]
5331
+ > async asBlob() {
5332
+ > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
5333
+ > const content = await response.blob();
5334
+ > return content;
5335
+ > },
5336
+ */
5337
+ async asJson() {
5338
+ // TODO: [👨🏻‍🤝‍👨🏻]
5339
+ const content = await response.json();
5340
+ return content;
5341
+ },
5342
+ async asText() {
5343
+ // TODO: [👨🏻‍🤝‍👨🏻]
5344
+ const content = await response.text();
5345
+ return content;
5346
+ },
5347
+ };
5348
+ }
5349
+ /**
5350
+ * Downloads a PDF knowledge source into cache when possible and falls back to in-memory content otherwise.
4937
5351
  *
4938
- * @public exported from `@promptbook/core`
5352
+ * @private internal utility of `makeKnowledgeSourceHandler`
4939
5353
  */
4940
- async function prepareKnowledgePieces(knowledgeSources, tools, options) {
4941
- const { maxParallelCount = DEFAULT_MAX_PARALLEL_COUNT, rootDirname, isVerbose = DEFAULT_IS_VERBOSE } = options;
4942
- const knowledgePreparedUnflatten = new Array(knowledgeSources.length);
4943
- await forEachAsync(knowledgeSources, { maxParallelCount }, async (knowledgeSource, index) => {
4944
- try {
4945
- let partialPieces = null;
4946
- const sourceHandler = await makeKnowledgeSourceHandler(knowledgeSource, tools, { rootDirname, isVerbose });
4947
- const scrapers = arrayableToArray(tools.scrapers);
4948
- for (const scraper of scrapers) {
4949
- if (!scraper.metadata.mimeTypes.includes(sourceHandler.mimeType)
4950
- // <- TODO: [🦔] Implement mime-type wildcards
4951
- ) {
5354
+ async function cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose) {
5355
+ const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5356
+ const filepath = createDownloadedKnowledgeSourceFilepath(url, mimeType);
5357
+ const fullFilepath = join(rootDirname, filepath);
5358
+ await createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose);
5359
+ const fileContent = Buffer.from(await response.arrayBuffer());
5360
+ if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5361
+ throw new LimitReachedError(`File is too large (${Math.round(fileContent.length / 1024 / 1024)}MB). Maximum allowed size is ${Math.round(DEFAULT_MAX_FILE_SIZE / 1024 / 1024)}MB.`);
5362
+ }
5363
+ const isCached = await writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose);
5364
+ if (!isCached) {
5365
+ return makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent);
5366
+ }
5367
+ // TODO: [💵] Check the file security
5368
+ // TODO: [🧹][🧠] Delete the file after the scraping is done
5369
+ if (isVerbose) {
5370
+ console.info(`📄 [5] "${name}" cached at "${fullFilepath}"`);
5371
+ }
5372
+ return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
5373
+ ...options,
5374
+ rootDirname,
5375
+ });
5376
+ }
5377
+ /**
5378
+ * Builds a stable cache filepath for a downloaded knowledge source.
5379
+ *
5380
+ * @private internal utility of `makeKnowledgeSourceHandler`
5381
+ */
5382
+ function createDownloadedKnowledgeSourceFilepath(url, mimeType) {
5383
+ const basename = url.split('/').pop() || titleToName(url);
5384
+ const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
5385
+ // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5386
+ return join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
5387
+ }
5388
+ /**
5389
+ * Tries to create the cache directory needed for a downloaded knowledge source.
5390
+ *
5391
+ * @private internal utility of `makeKnowledgeSourceHandler`
5392
+ */
5393
+ async function createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose) {
5394
+ try {
5395
+ await tools.fs.mkdir(dirname(fullFilepath), { recursive: true });
5396
+ }
5397
+ catch (error) {
5398
+ if (isVerbose) {
5399
+ console.info(`📄 [3] "${name}" error creating cache directory`);
5400
+ }
5401
+ // Note: If we can't create cache directory, we'll handle it when trying to write the file
5402
+ // This handles read-only filesystems, permission issues, and missing parent directories
5403
+ if (!isIgnorableCacheFilesystemError(error)) {
5404
+ throw error;
5405
+ }
5406
+ }
5407
+ }
5408
+ /**
5409
+ * Tries to write downloaded content into cache and reports whether the cache was usable.
5410
+ *
5411
+ * @private internal utility of `makeKnowledgeSourceHandler`
5412
+ */
5413
+ async function writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose) {
5414
+ // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
5415
+ try {
5416
+ await tools.fs.writeFile(fullFilepath, fileContent);
5417
+ return true;
5418
+ }
5419
+ catch (error) {
5420
+ if (isVerbose) {
5421
+ console.info(`📄 [4] "${name}" error writing cache file`);
5422
+ }
5423
+ // Note: If we can't write to cache, we'll process the file directly from memory
5424
+ // This handles read-only filesystems like Vercel
5425
+ if (isIgnorableCacheFilesystemError(error)) {
5426
+ return false;
5427
+ }
5428
+ throw error;
5429
+ }
5430
+ }
5431
+ /**
5432
+ * Detects filesystem errors that should not fail optional caching.
5433
+ *
5434
+ * @private internal utility of `makeKnowledgeSourceHandler`
5435
+ */
5436
+ function isIgnorableCacheFilesystemError(error) {
5437
+ return (error instanceof Error &&
5438
+ (error.message.includes('EROFS') ||
5439
+ error.message.includes('read-only') ||
5440
+ error.message.includes('EACCES') ||
5441
+ error.message.includes('EPERM') ||
5442
+ error.message.includes('ENOENT')));
5443
+ }
5444
+ /**
5445
+ * Creates a source handler backed by already downloaded file content kept in memory.
5446
+ *
5447
+ * @private internal utility of `makeKnowledgeSourceHandler`
5448
+ */
5449
+ function makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent) {
5450
+ return {
5451
+ source: name,
5452
+ filename: null,
5453
+ url,
5454
+ mimeType,
5455
+ async asJson() {
5456
+ return JSON.parse(fileContent.toString('utf-8'));
5457
+ },
5458
+ async asText() {
5459
+ return fileContent.toString('utf-8');
5460
+ },
5461
+ };
5462
+ }
5463
+ /**
5464
+ * Creates a source handler for file-based knowledge.
5465
+ *
5466
+ * @private internal utility of `makeKnowledgeSourceHandler`
5467
+ */
5468
+ async function makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose) {
5469
+ if (tools.fs === undefined) {
5470
+ throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
5471
+ // <- TODO: [🧠] What is the best error type here`
5472
+ }
5473
+ if (rootDirname === null) {
5474
+ throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
5475
+ // <- TODO: [🧠] What is the best error type here`
5476
+ }
5477
+ const filename = isAbsolute(knowledgeSourceContent)
5478
+ ? knowledgeSourceContent
5479
+ : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5480
+ if (isVerbose) {
5481
+ console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5482
+ }
5483
+ const fileExtension = getFileExtension(filename);
5484
+ const mimeType = extensionToMimeType(fileExtension || '');
5485
+ if (!(await isFileExisting(filename, tools.fs))) {
5486
+ throw new NotFoundError(spaceTrim$1((block) => `
5487
+ Can not make source handler for file which does not exist:
5488
+
5489
+ File:
5490
+ ${block(knowledgeSourceContent)}
5491
+
5492
+ Full file path:
5493
+ ${block(filename)}
5494
+ `));
5495
+ }
5496
+ // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
5497
+ return {
5498
+ source: name,
5499
+ filename,
5500
+ url: null,
5501
+ mimeType,
5502
+ /*
5503
+ TODO: [🥽]
5504
+ > async asBlob() {
5505
+ > const content = await tools.fs!.readFile(filename);
5506
+ > return new Blob(
5507
+ > [
5508
+ > content,
5509
+ > // <- TODO: [🥽] This is NOT tested, test it
5510
+ > ],
5511
+ > { type: mimeType },
5512
+ > );
5513
+ > },
5514
+ */
5515
+ async asJson() {
5516
+ return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
5517
+ },
5518
+ async asText() {
5519
+ return await tools.fs.readFile(filename, 'utf-8');
5520
+ },
5521
+ };
5522
+ }
5523
+ /**
5524
+ * Creates a source handler for inline text knowledge.
5525
+ *
5526
+ * @private internal utility of `makeKnowledgeSourceHandler`
5527
+ */
5528
+ function makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose) {
5529
+ if (isVerbose) {
5530
+ console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
5531
+ console.info('---');
5532
+ console.info(knowledgeSourceContent);
5533
+ console.info('---');
5534
+ }
5535
+ return {
5536
+ source: name,
5537
+ filename: null,
5538
+ url: null,
5539
+ mimeType: 'text/markdown',
5540
+ asText() {
5541
+ return knowledgeSourceContent;
5542
+ },
5543
+ asJson() {
5544
+ throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
5545
+ },
5546
+ /*
5547
+ TODO: [🥽]
5548
+ > asBlob() {
5549
+ > throw new UnexpectedError(
5550
+ > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
5551
+ > );
5552
+ > },
5553
+ */
5554
+ };
5555
+ }
5556
+
5557
+ /**
5558
+ * Prepares the knowledge pieces
5559
+ *
5560
+ * @see https://github.com/webgptorg/promptbook/discussions/41
5561
+ *
5562
+ * @public exported from `@promptbook/core`
5563
+ */
5564
+ async function prepareKnowledgePieces(knowledgeSources, tools, options) {
5565
+ const { maxParallelCount = DEFAULT_MAX_PARALLEL_COUNT, rootDirname, isVerbose = DEFAULT_IS_VERBOSE } = options;
5566
+ const knowledgePreparedUnflatten = new Array(knowledgeSources.length);
5567
+ await forEachAsync(knowledgeSources, { maxParallelCount }, async (knowledgeSource, index) => {
5568
+ try {
5569
+ let partialPieces = null;
5570
+ const sourceHandler = await makeKnowledgeSourceHandler(knowledgeSource, tools, { rootDirname, isVerbose });
5571
+ const scrapers = arrayableToArray(tools.scrapers);
5572
+ for (const scraper of scrapers) {
5573
+ if (!scraper.metadata.mimeTypes.includes(sourceHandler.mimeType)
5574
+ // <- TODO: [🦔] Implement mime-type wildcards
5575
+ ) {
4952
5576
  continue;
4953
5577
  }
4954
5578
  const partialPiecesUnchecked = await scraper.scrape(sourceHandler);
@@ -6112,112 +6736,10 @@ function validatePromptResult(options) {
6112
6736
  }
6113
6737
  }
6114
6738
 
6115
- /**
6116
- * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
6117
- * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
6118
- * Throws errors if execution fails after all attempts.
6119
- *
6120
- * @param options - The options for execution, including task, parameters, pipeline, and configuration.
6121
- * @returns The result string of the executed task.
6122
- *
6123
- * @private internal utility of `createPipelineExecutor`
6124
- */
6125
- async function executeAttempts(options) {
6126
- const $ongoingTaskResult = createOngoingTaskResult();
6127
- const llmTools = getSingleLlmExecutionTools(options.tools.llm);
6128
- attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
6129
- const attempt = createAttemptDescriptor({
6130
- attemptIndex,
6131
- jokerParameterNames: options.jokerParameterNames,
6132
- pipelineIdentification: options.pipelineIdentification,
6133
- });
6134
- resetAttemptExecutionState($ongoingTaskResult);
6135
- try {
6136
- await executeSingleAttempt({
6137
- attempt,
6138
- options,
6139
- llmTools,
6140
- $ongoingTaskResult,
6141
- });
6142
- break attempts;
6143
- }
6144
- catch (error) {
6145
- if (!(error instanceof ExpectError)) {
6146
- throw error;
6147
- }
6148
- recordFailedAttempt({
6149
- error,
6150
- attemptIndex,
6151
- onProgress: options.onProgress,
6152
- $ongoingTaskResult,
6153
- });
6154
- }
6155
- finally {
6156
- reportPromptExecution({
6157
- attempt,
6158
- task: options.task,
6159
- $executionReport: options.$executionReport,
6160
- logLlmCall: options.logLlmCall,
6161
- $ongoingTaskResult,
6162
- });
6163
- }
6164
- throwIfFinalAttemptFailed({
6165
- attemptIndex,
6166
- maxAttempts: options.maxAttempts,
6167
- maxExecutionAttempts: options.maxExecutionAttempts,
6168
- pipelineIdentification: options.pipelineIdentification,
6169
- $ongoingTaskResult,
6170
- });
6171
- }
6172
- return getSuccessfulResultString({
6173
- pipelineIdentification: options.pipelineIdentification,
6174
- $ongoingTaskResult,
6175
- });
6176
- }
6177
- /**
6178
- * Creates mutable attempt state for one task execution lifecycle.
6179
- */
6180
- function createOngoingTaskResult() {
6181
- return {
6182
- $result: null,
6183
- $resultString: null,
6184
- $expectError: null,
6185
- $scriptPipelineExecutionErrors: [],
6186
- $failedResults: [],
6187
- };
6188
- }
6189
- /**
6190
- * Resolves the bookkeeping for one loop iteration, including joker lookup.
6191
- */
6192
- function createAttemptDescriptor(options) {
6193
- const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
6194
- const isJokerAttempt = attemptIndex < 0;
6195
- const jokerParameterName = isJokerAttempt
6196
- ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
6197
- : undefined;
6198
- if (isJokerAttempt && !jokerParameterName) {
6199
- throw new UnexpectedError(spaceTrim$1((block) => `
6200
- Joker not found in attempt ${attemptIndex}
6201
-
6202
- ${block(pipelineIdentification)}
6203
- `));
6204
- }
6205
- return {
6206
- attemptIndex,
6207
- isJokerAttempt,
6208
- jokerParameterName,
6209
- };
6210
- }
6211
- /**
6212
- * Clears the per-attempt result slots while preserving cumulative failure history.
6213
- */
6214
- function resetAttemptExecutionState($ongoingTaskResult) {
6215
- $ongoingTaskResult.$result = null;
6216
- $ongoingTaskResult.$resultString = null;
6217
- $ongoingTaskResult.$expectError = null;
6218
- }
6219
6739
  /**
6220
6740
  * Executes one loop iteration, from joker resolution or task execution through validation.
6741
+ *
6742
+ * @private function of `executeAttempts`
6221
6743
  */
6222
6744
  async function executeSingleAttempt(options) {
6223
6745
  const { attempt, options: executeAttemptsOptions, llmTools, $ongoingTaskResult } = options;
@@ -6532,11 +7054,15 @@ function validateAttemptResult(options) {
6532
7054
  // Update the result string in case format processing modified it (e.g., JSON extraction)
6533
7055
  $ongoingTaskResult.$resultString = validationResult.processedResultString;
6534
7056
  }
7057
+
6535
7058
  /**
6536
- * Stores one failed attempt and reports the expectation error upstream.
7059
+ * Stores one failed attempt, reports the expectation error, and throws the aggregated retry error after the final
7060
+ * regular attempt.
7061
+ *
7062
+ * @private function of `executeAttempts`
6537
7063
  */
6538
- function recordFailedAttempt(options) {
6539
- const { error, attemptIndex, onProgress, $ongoingTaskResult } = options;
7064
+ function handleAttemptFailure(options) {
7065
+ const { error, attemptIndex, maxAttempts, maxExecutionAttempts, onProgress, pipelineIdentification, $ongoingTaskResult, } = options;
6540
7066
  $ongoingTaskResult.$expectError = error;
6541
7067
  $ongoingTaskResult.$failedResults.push({
6542
7068
  attemptIndex,
@@ -6546,39 +7072,7 @@ function recordFailedAttempt(options) {
6546
7072
  onProgress({
6547
7073
  errors: [error],
6548
7074
  });
6549
- }
6550
- /**
6551
- * Appends the prompt execution report for prompt-task attempts.
6552
- */
6553
- function reportPromptExecution(options) {
6554
- const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
6555
- if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
6556
- return;
6557
- }
6558
- // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
6559
- // In that case we don’t want to make a report about it because it’s not a llm execution error
6560
- const executionPromptReport = {
6561
- prompt: {
6562
- ...$ongoingTaskResult.$prompt,
6563
- // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
6564
- },
6565
- result: $ongoingTaskResult.$result || undefined,
6566
- error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
6567
- };
6568
- $executionReport.promptExecutions.push(executionPromptReport);
6569
- if (logLlmCall) {
6570
- logLlmCall({
6571
- modelName: 'model' /* <- TODO: How to get model name from the report */,
6572
- report: executionPromptReport,
6573
- });
6574
- }
6575
- }
6576
- /**
6577
- * Throws the aggregated retry error after the last regular attempt fails expectations.
6578
- */
6579
- function throwIfFinalAttemptFailed(options) {
6580
- const { attemptIndex, maxAttempts, maxExecutionAttempts, pipelineIdentification, $ongoingTaskResult } = options;
6581
- if ($ongoingTaskResult.$expectError === null || attemptIndex !== maxAttempts - 1) {
7075
+ if (attemptIndex !== maxAttempts - 1) {
6582
7076
  return;
6583
7077
  }
6584
7078
  throw new PipelineExecutionError(spaceTrim$1((block) => {
@@ -6609,7 +7103,9 @@ function createFailuresSummary($failedResults) {
6609
7103
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
6610
7104
 
6611
7105
  Result:
6612
- ${block(failure.result === null ? 'null' : quoteMultilineText(spaceTrim$1(failure.result)))}
7106
+ ${block(failure.result === null
7107
+ ? 'null'
7108
+ : quoteMultilineText(spaceTrim$1(failure.result)))}
6613
7109
  `;
6614
7110
  }))
6615
7111
  .join('\n\n---\n\n');
@@ -6623,6 +7119,136 @@ function quoteMultilineText(text) {
6623
7119
  .map((line) => `> ${line}`)
6624
7120
  .join('\n');
6625
7121
  }
7122
+
7123
+ /**
7124
+ * Appends the prompt execution report for prompt-task attempts.
7125
+ *
7126
+ * @private function of `executeAttempts`
7127
+ */
7128
+ function reportPromptExecution(options) {
7129
+ const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
7130
+ if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
7131
+ return;
7132
+ }
7133
+ // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
7134
+ // In that case we don’t want to make a report about it because it’s not a llm execution error
7135
+ const executionPromptReport = {
7136
+ prompt: {
7137
+ ...$ongoingTaskResult.$prompt,
7138
+ // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
7139
+ },
7140
+ result: $ongoingTaskResult.$result || undefined,
7141
+ error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
7142
+ };
7143
+ $executionReport.promptExecutions.push(executionPromptReport);
7144
+ if (logLlmCall) {
7145
+ logLlmCall({
7146
+ modelName: 'model' /* <- TODO: How to get model name from the report */,
7147
+ report: executionPromptReport,
7148
+ });
7149
+ }
7150
+ }
7151
+
7152
+ /**
7153
+ * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
7154
+ * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
7155
+ * Throws errors if execution fails after all attempts.
7156
+ *
7157
+ * @param options - The options for execution, including task, parameters, pipeline, and configuration.
7158
+ * @returns The result string of the executed task.
7159
+ *
7160
+ * @private internal utility of `createPipelineExecutor`
7161
+ */
7162
+ async function executeAttempts(options) {
7163
+ const $ongoingTaskResult = createOngoingTaskResult();
7164
+ const llmTools = getSingleLlmExecutionTools(options.tools.llm);
7165
+ attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
7166
+ const attempt = createAttemptDescriptor({
7167
+ attemptIndex,
7168
+ jokerParameterNames: options.jokerParameterNames,
7169
+ pipelineIdentification: options.pipelineIdentification,
7170
+ });
7171
+ resetAttemptExecutionState($ongoingTaskResult);
7172
+ try {
7173
+ await executeSingleAttempt({
7174
+ attempt,
7175
+ options,
7176
+ llmTools,
7177
+ $ongoingTaskResult,
7178
+ });
7179
+ break attempts;
7180
+ }
7181
+ catch (error) {
7182
+ if (!(error instanceof ExpectError)) {
7183
+ throw error;
7184
+ }
7185
+ handleAttemptFailure({
7186
+ error,
7187
+ attemptIndex,
7188
+ maxAttempts: options.maxAttempts,
7189
+ maxExecutionAttempts: options.maxExecutionAttempts,
7190
+ onProgress: options.onProgress,
7191
+ pipelineIdentification: options.pipelineIdentification,
7192
+ $ongoingTaskResult,
7193
+ });
7194
+ }
7195
+ finally {
7196
+ reportPromptExecution({
7197
+ attempt,
7198
+ task: options.task,
7199
+ $executionReport: options.$executionReport,
7200
+ logLlmCall: options.logLlmCall,
7201
+ $ongoingTaskResult,
7202
+ });
7203
+ }
7204
+ }
7205
+ return getSuccessfulResultString({
7206
+ pipelineIdentification: options.pipelineIdentification,
7207
+ $ongoingTaskResult,
7208
+ });
7209
+ }
7210
+ /**
7211
+ * Creates mutable attempt state for one task execution lifecycle.
7212
+ */
7213
+ function createOngoingTaskResult() {
7214
+ return {
7215
+ $result: null,
7216
+ $resultString: null,
7217
+ $expectError: null,
7218
+ $scriptPipelineExecutionErrors: [],
7219
+ $failedResults: [],
7220
+ };
7221
+ }
7222
+ /**
7223
+ * Resolves the bookkeeping for one loop iteration, including joker lookup.
7224
+ */
7225
+ function createAttemptDescriptor(options) {
7226
+ const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
7227
+ const isJokerAttempt = attemptIndex < 0;
7228
+ const jokerParameterName = isJokerAttempt
7229
+ ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
7230
+ : undefined;
7231
+ if (isJokerAttempt && !jokerParameterName) {
7232
+ throw new UnexpectedError(spaceTrim$1((block) => `
7233
+ Joker not found in attempt ${attemptIndex}
7234
+
7235
+ ${block(pipelineIdentification)}
7236
+ `));
7237
+ }
7238
+ return {
7239
+ attemptIndex,
7240
+ isJokerAttempt,
7241
+ jokerParameterName,
7242
+ };
7243
+ }
7244
+ /**
7245
+ * Clears the per-attempt result slots while preserving cumulative failure history.
7246
+ */
7247
+ function resetAttemptExecutionState($ongoingTaskResult) {
7248
+ $ongoingTaskResult.$result = null;
7249
+ $ongoingTaskResult.$resultString = null;
7250
+ $ongoingTaskResult.$expectError = null;
7251
+ }
6626
7252
  /**
6627
7253
  * Returns the successful result string or raises an unexpected internal-state error.
6628
7254
  */