@promptbook/website-crawler 0.112.0-72 → 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 (161) hide show
  1. package/esm/index.es.js +1425 -799
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/book-components/Chat/save/_common/chatExportRendering.d.ts +28 -0
  4. package/esm/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  5. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +1 -1
  6. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  7. package/esm/src/book-components/Chat/save/index.d.ts +4 -4
  8. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +1 -1
  9. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  10. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -2
  11. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +2 -2
  12. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  13. package/esm/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  14. package/esm/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  15. package/esm/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +2 -0
  16. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  17. package/esm/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  18. package/esm/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  19. package/esm/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  20. package/esm/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  21. package/esm/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  22. package/esm/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  23. package/esm/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  24. package/esm/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  25. package/esm/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  26. package/esm/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  27. package/esm/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  28. package/esm/src/execution/resolveTaskTldr.d.ts +32 -0
  29. package/esm/src/execution/resolveTaskTldr.test.d.ts +1 -0
  30. package/esm/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  31. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  32. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  33. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  34. package/esm/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  35. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  36. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  37. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  38. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  39. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  40. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  41. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  42. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  43. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  44. package/esm/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  45. package/esm/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  46. package/esm/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  47. package/esm/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  48. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  49. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  50. package/esm/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  51. package/esm/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  52. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  53. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  54. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  55. package/esm/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  56. package/esm/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  57. package/esm/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  58. package/esm/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  59. package/esm/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  60. package/esm/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  61. package/esm/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  62. package/esm/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  63. package/esm/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  64. package/esm/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  65. package/esm/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  66. package/esm/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  67. package/esm/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  68. package/esm/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  69. package/esm/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  70. package/esm/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  71. package/esm/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  72. package/esm/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  73. package/esm/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  74. package/esm/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  75. package/esm/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  76. package/esm/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  77. package/esm/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  78. package/esm/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  79. package/esm/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  80. package/esm/src/version.d.ts +1 -1
  81. package/package.json +2 -2
  82. package/umd/index.umd.js +1425 -799
  83. package/umd/index.umd.js.map +1 -1
  84. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +28 -0
  85. package/umd/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  86. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +1 -1
  87. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  88. package/umd/src/book-components/Chat/save/index.d.ts +4 -4
  89. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +1 -1
  90. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  91. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +3 -2
  92. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +2 -2
  93. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  94. package/umd/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  95. package/umd/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  96. package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +2 -0
  97. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  98. package/umd/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  99. package/umd/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  100. package/umd/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  101. package/umd/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  102. package/umd/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  103. package/umd/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  104. package/umd/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  105. package/umd/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  106. package/umd/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  107. package/umd/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  108. package/umd/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  109. package/umd/src/execution/resolveTaskTldr.d.ts +32 -0
  110. package/umd/src/execution/resolveTaskTldr.test.d.ts +1 -0
  111. package/umd/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  112. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  113. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  114. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  115. package/umd/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  116. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  117. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  118. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  119. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  120. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  121. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  122. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  123. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  124. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  125. package/umd/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  126. package/umd/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  127. package/umd/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  128. package/umd/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  129. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  130. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  131. package/umd/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  132. package/umd/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  133. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  134. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  135. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  136. package/umd/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  137. package/umd/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  138. package/umd/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  139. package/umd/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  140. package/umd/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  141. package/umd/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  142. package/umd/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  143. package/umd/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  144. package/umd/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  145. package/umd/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  146. package/umd/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  147. package/umd/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  148. package/umd/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  149. package/umd/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  150. package/umd/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  151. package/umd/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  152. package/umd/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  153. package/umd/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  154. package/umd/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  155. package/umd/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  156. package/umd/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  157. package/umd/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  158. package/umd/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  159. package/umd/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  160. package/umd/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  161. package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js CHANGED
@@ -23,7 +23,7 @@
23
23
  * @generated
24
24
  * @see https://github.com/webgptorg/promptbook
25
25
  */
26
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
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
@@ -1772,6 +1772,60 @@
1772
1772
  }
1773
1773
  // TODO: [🧠][🈴] Where is the best location for this file
1774
1774
 
1775
+ /**
1776
+ * Appends one markdown block to an existing markdown document.
1777
+ *
1778
+ * @private internal utility of `pipelineJsonToString`
1779
+ */
1780
+ function appendMarkdownBlock(pipelineString, markdownBlock) {
1781
+ return spacetrim.spaceTrim((block) => `
1782
+ ${block(pipelineString)}
1783
+
1784
+ ${block(markdownBlock)}
1785
+ `);
1786
+ }
1787
+
1788
+ /**
1789
+ * Collects pipeline-level commands in the existing serialization order.
1790
+ *
1791
+ * @private internal utility of `pipelineJsonToString`
1792
+ */
1793
+ function createPipelineCommands(pipelineJson) {
1794
+ const { pipelineUrl, bookVersion, parameters } = pipelineJson;
1795
+ const commands = [];
1796
+ if (pipelineUrl) {
1797
+ commands.push(`PIPELINE URL ${pipelineUrl}`);
1798
+ }
1799
+ if (bookVersion !== `undefined`) {
1800
+ commands.push(`BOOK VERSION ${bookVersion}`);
1801
+ }
1802
+ commands.push(...createParameterCommands(parameters, 'INPUT PARAMETER', ({ isInput }) => isInput));
1803
+ commands.push(...createParameterCommands(parameters, 'OUTPUT PARAMETER', ({ isOutput }) => isOutput));
1804
+ return commands;
1805
+ }
1806
+ /**
1807
+ * Builds one group of parameter commands while preserving the original parameter order.
1808
+ *
1809
+ * @private internal utility of `createPipelineCommands`
1810
+ */
1811
+ function createParameterCommands(parameters, commandPrefix, isIncluded) {
1812
+ return parameters
1813
+ .filter((parameter) => isIncluded(parameter))
1814
+ .map((parameter) => `${commandPrefix} ${parameterJsonToString(parameter)}`);
1815
+ }
1816
+ /**
1817
+ * Converts one parameter JSON declaration to the serialized inline form.
1818
+ *
1819
+ * @private internal utility of `createPipelineCommands`
1820
+ */
1821
+ function parameterJsonToString(parameterJson) {
1822
+ const { name, description } = parameterJson;
1823
+ if (!description) {
1824
+ return `{${name}}`;
1825
+ }
1826
+ return `{${name}} ${description}`;
1827
+ }
1828
+
1775
1829
  /**
1776
1830
  * Prettify the html code
1777
1831
  *
@@ -1785,6 +1839,31 @@
1785
1839
  return (content + `\n\n<!-- Note: Prettier removed from Promptbook -->`);
1786
1840
  }
1787
1841
 
1842
+ /**
1843
+ * Creates the initial markdown heading and description of a pipeline.
1844
+ *
1845
+ * @private internal utility of `pipelineJsonToString`
1846
+ */
1847
+ function createPipelineIntroduction(pipelineJson) {
1848
+ const { title, description } = pipelineJson;
1849
+ const pipelineIntroduction = spacetrim.spaceTrim((block) => `
1850
+ # ${title}
1851
+
1852
+ ${block(description || '')}
1853
+ `);
1854
+ // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1855
+ return prettifyMarkdown(pipelineIntroduction);
1856
+ }
1857
+
1858
+ /**
1859
+ * Renders commands as markdown bullet items.
1860
+ *
1861
+ * @private internal utility of `pipelineJsonToString`
1862
+ */
1863
+ function stringifyCommands(commands) {
1864
+ return commands.map((command) => `- ${command}`).join('\n');
1865
+ }
1866
+
1788
1867
  /**
1789
1868
  * Makes first letter of a string uppercase
1790
1869
  *
@@ -1797,141 +1876,186 @@
1797
1876
  }
1798
1877
 
1799
1878
  /**
1800
- * Converts promptbook in JSON format to string format
1879
+ * Collects all task-specific serialization details.
1801
1880
  *
1802
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1803
- * @param pipelineJson Promptbook in JSON format (.bookc)
1804
- * @returns Promptbook in string format (.book.md)
1881
+ * @private internal utility of `pipelineJsonToString`
1882
+ */
1883
+ function createTaskSerialization(task) {
1884
+ const taskTypeSerialization = createTaskTypeSerialization(task);
1885
+ return {
1886
+ commands: [
1887
+ ...taskTypeSerialization.commands,
1888
+ ...createJokerCommands(task),
1889
+ ...createPostprocessingCommands(task),
1890
+ ...createExpectationCommands(task),
1891
+ ...createFormatCommands(task),
1892
+ ],
1893
+ contentLanguage: taskTypeSerialization.contentLanguage,
1894
+ };
1895
+ }
1896
+ /**
1897
+ * Collects commands and content language driven by the task type.
1805
1898
  *
1806
- * @public exported from `@promptbook/core`
1899
+ * @private internal utility of `createTaskSerialization`
1807
1900
  */
1808
- function pipelineJsonToString(pipelineJson) {
1809
- const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
1810
- let pipelineString = spacetrim.spaceTrim((block) => `
1811
- # ${title}
1812
-
1813
- ${block(description || '')}
1814
- `);
1901
+ function createTaskTypeSerialization(task) {
1902
+ if (task.taskType === 'PROMPT_TASK') {
1903
+ return {
1904
+ commands: createPromptTaskCommands(task),
1905
+ contentLanguage: 'text',
1906
+ };
1907
+ }
1908
+ if (task.taskType === 'SIMPLE_TASK') {
1909
+ return {
1910
+ commands: ['SIMPLE TEMPLATE'],
1911
+ contentLanguage: 'text',
1912
+ };
1913
+ }
1914
+ if (task.taskType === 'SCRIPT_TASK') {
1915
+ return {
1916
+ commands: ['SCRIPT'],
1917
+ contentLanguage: task.contentLanguage || '',
1918
+ };
1919
+ }
1920
+ if (task.taskType === 'DIALOG_TASK') {
1921
+ return {
1922
+ commands: ['DIALOG'],
1923
+ contentLanguage: 'text',
1924
+ };
1925
+ }
1926
+ return {
1927
+ commands: [],
1928
+ contentLanguage: 'text',
1929
+ };
1930
+ }
1931
+ /**
1932
+ * Collects prompt-task-specific commands.
1933
+ *
1934
+ * @private internal utility of `createTaskSerialization`
1935
+ */
1936
+ function createPromptTaskCommands(task) {
1937
+ const { modelName, modelVariant } = task.modelRequirements || {};
1815
1938
  const commands = [];
1816
- if (pipelineUrl) {
1817
- commands.push(`PIPELINE URL ${pipelineUrl}`);
1939
+ // Note: Do nothing, it is default
1940
+ // commands.push(`PROMPT`);
1941
+ if (modelVariant) {
1942
+ commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1818
1943
  }
1819
- if (bookVersion !== `undefined`) {
1820
- commands.push(`BOOK VERSION ${bookVersion}`);
1944
+ if (modelName) {
1945
+ commands.push(`MODEL NAME \`${modelName}\``);
1821
1946
  }
1822
- // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1823
- pipelineString = prettifyMarkdown(pipelineString);
1824
- for (const parameter of parameters.filter(({ isInput }) => isInput)) {
1825
- commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
1947
+ return commands;
1948
+ }
1949
+ /**
1950
+ * Collects joker commands.
1951
+ *
1952
+ * @private internal utility of `createTaskSerialization`
1953
+ */
1954
+ function createJokerCommands(task) {
1955
+ var _a;
1956
+ return ((_a = task.jokerParameterNames) === null || _a === void 0 ? void 0 : _a.map((joker) => `JOKER {${joker}}`)) || [];
1957
+ }
1958
+ /**
1959
+ * Collects postprocessing commands.
1960
+ *
1961
+ * @private internal utility of `createTaskSerialization`
1962
+ */
1963
+ function createPostprocessingCommands(task) {
1964
+ var _a;
1965
+ return ((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || [];
1966
+ }
1967
+ /**
1968
+ * Collects expectation commands.
1969
+ *
1970
+ * @private internal utility of `createTaskSerialization`
1971
+ */
1972
+ function createExpectationCommands(task) {
1973
+ if (!task.expectations) {
1974
+ return [];
1826
1975
  }
1827
- for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
1828
- commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
1976
+ return Object.entries(task.expectations).flatMap(([unit, expectation]) => createExpectationCommandsForUnit(unit, expectation.min, expectation.max));
1977
+ }
1978
+ /**
1979
+ * Collects expectation commands for a single unit.
1980
+ *
1981
+ * @private internal utility of `createTaskSerialization`
1982
+ */
1983
+ function createExpectationCommandsForUnit(unit, min, max) {
1984
+ if (min === max) {
1985
+ return [`EXPECT EXACTLY ${min} ${formatExpectationUnit(unit, min)}`];
1829
1986
  }
1830
- pipelineString = spacetrim.spaceTrim((block) => `
1831
- ${block(pipelineString)}
1832
-
1833
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
1834
- `);
1835
- for (const task of tasks) {
1836
- const {
1837
- /* Note: Not using:> name, */
1838
- title, description,
1839
- /* Note: dependentParameterNames, */
1840
- jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
1841
- const commands = [];
1842
- let contentLanguage = 'text';
1843
- if (taskType === 'PROMPT_TASK') {
1844
- const { modelRequirements } = task;
1845
- const { modelName, modelVariant } = modelRequirements || {};
1846
- // Note: Do nothing, it is default
1847
- // commands.push(`PROMPT`);
1848
- if (modelVariant) {
1849
- commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1850
- }
1851
- if (modelName) {
1852
- commands.push(`MODEL NAME \`${modelName}\``);
1853
- }
1854
- }
1855
- else if (taskType === 'SIMPLE_TASK') {
1856
- commands.push(`SIMPLE TEMPLATE`);
1857
- // Note: Nothing special here
1858
- }
1859
- else if (taskType === 'SCRIPT_TASK') {
1860
- commands.push(`SCRIPT`);
1861
- if (task.contentLanguage) {
1862
- contentLanguage = task.contentLanguage;
1863
- }
1864
- else {
1865
- contentLanguage = '';
1866
- }
1867
- }
1868
- else if (taskType === 'DIALOG_TASK') {
1869
- commands.push(`DIALOG`);
1870
- // Note: Nothing special here
1871
- } // <- }else if([🅱]
1872
- if (jokers) {
1873
- for (const joker of jokers) {
1874
- commands.push(`JOKER {${joker}}`);
1875
- }
1876
- } /* not else */
1877
- if (postprocessing) {
1878
- for (const postprocessingFunctionName of postprocessing) {
1879
- commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
1880
- }
1881
- } /* not else */
1882
- if (expectations) {
1883
- for (const [unit, { min, max }] of Object.entries(expectations)) {
1884
- if (min === max) {
1885
- commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1886
- }
1887
- else {
1888
- if (min !== undefined) {
1889
- commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1890
- } /* not else */
1891
- if (max !== undefined) {
1892
- commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
1893
- }
1894
- }
1895
- }
1896
- } /* not else */
1897
- if (format) {
1898
- if (format === 'JSON') {
1899
- // TODO: @deprecated remove
1900
- commands.push(`FORMAT JSON`);
1901
- }
1902
- } /* not else */
1903
- pipelineString = spacetrim.spaceTrim((block) => `
1904
- ${block(pipelineString)}
1987
+ const commands = [];
1988
+ if (min !== undefined) {
1989
+ commands.push(`EXPECT MIN ${min} ${formatExpectationUnit(unit, min)}`);
1990
+ }
1991
+ if (max !== undefined) {
1992
+ commands.push(`EXPECT MAX ${max} ${formatExpectationUnit(unit, max)}`);
1993
+ }
1994
+ return commands;
1995
+ }
1996
+ /**
1997
+ * Formats the expectation unit exactly as the legacy serializer does.
1998
+ *
1999
+ * @private internal utility of `createTaskSerialization`
2000
+ */
2001
+ function formatExpectationUnit(unit, amount) {
2002
+ return capitalize(unit + (amount > 1 ? 's' : ''));
2003
+ }
2004
+ /**
2005
+ * Collects format commands.
2006
+ *
2007
+ * @private internal utility of `createTaskSerialization`
2008
+ */
2009
+ function createFormatCommands(task) {
2010
+ if (task.format === 'JSON') {
2011
+ // TODO: @deprecated remove
2012
+ return ['FORMAT JSON'];
2013
+ }
2014
+ return [];
2015
+ }
1905
2016
 
1906
- ## ${title}
2017
+ /**
2018
+ * Stringifies one task section of the pipeline.
2019
+ *
2020
+ * @private internal utility of `pipelineJsonToString`
2021
+ */
2022
+ function stringifyTask(task) {
2023
+ const { title, description, content, resultingParameterName } = task;
2024
+ const { commands, contentLanguage } = createTaskSerialization(task);
2025
+ return spacetrim.spaceTrim((block) => `
2026
+ ## ${title}
1907
2027
 
1908
- ${block(description || '')}
2028
+ ${block(description || '')}
1909
2029
 
1910
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
2030
+ ${block(stringifyCommands(commands))}
1911
2031
 
1912
- \`\`\`${contentLanguage}
1913
- ${block(spacetrim.spaceTrim(content))}
1914
- \`\`\`
2032
+ \`\`\`${contentLanguage}
2033
+ ${block(spacetrim.spaceTrim(content))}
2034
+ \`\`\`
1915
2035
 
1916
- \`-> {${resultingParameterName}}\`
1917
- `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1918
- // <- TODO: [main] !!3 Escape
1919
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1920
- }
1921
- return validatePipelineString(pipelineString);
2036
+ \`-> {${resultingParameterName}}\`
2037
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
2038
+ // <- TODO: [main] !!3 Escape
2039
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1922
2040
  }
2041
+
1923
2042
  /**
1924
- * Handles task parameter Json to string.
2043
+ * Converts promptbook in JSON format to string format
1925
2044
  *
1926
- * @private internal utility of `pipelineJsonToString`
2045
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
2046
+ * @param pipelineJson Promptbook in JSON format (.bookc)
2047
+ * @returns Promptbook in string format (.book.md)
2048
+ *
2049
+ * @public exported from `@promptbook/core`
1927
2050
  */
1928
- function taskParameterJsonToString(taskParameterJson) {
1929
- const { name, description } = taskParameterJson;
1930
- let parameterString = `{${name}}`;
1931
- if (description) {
1932
- parameterString = `${parameterString} ${description}`;
2051
+ function pipelineJsonToString(pipelineJson) {
2052
+ let pipelineString = createPipelineIntroduction(pipelineJson);
2053
+ const pipelineCommands = createPipelineCommands(pipelineJson);
2054
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
2055
+ for (const task of pipelineJson.tasks) {
2056
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
1933
2057
  }
1934
- return parameterString;
2058
+ return validatePipelineString(pipelineString);
1935
2059
  }
1936
2060
  // TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
1937
2061
  // TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
@@ -2345,233 +2469,519 @@
2345
2469
  */
2346
2470
  function validatePipeline_InnerFunction(pipeline) {
2347
2471
  // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
2348
- const pipelineIdentification = (() => {
2349
- // Note: This is a 😐 implementation of [🚞]
2350
- const _ = [];
2351
- if (pipeline.sourceFile !== undefined) {
2352
- _.push(`File: ${pipeline.sourceFile}`);
2353
- }
2354
- if (pipeline.pipelineUrl !== undefined) {
2355
- _.push(`Url: ${pipeline.pipelineUrl}`);
2472
+ const context = createPipelineValidationContext(pipeline);
2473
+ validatePipelineMetadata(context);
2474
+ validatePipelineCollectionsStructure(context);
2475
+ validatePipelineParameters(context);
2476
+ validatePipelineTasks(context);
2477
+ validatePipelineDependencyResolution(context);
2478
+ // Note: Check that formfactor is corresponding to the pipeline interface
2479
+ // TODO: !!6 Implement this
2480
+ // pipeline.formfactorName
2481
+ }
2482
+ /**
2483
+ * Creates the shared validation context for one pipeline.
2484
+ *
2485
+ * @private internal utility of `validatePipeline`
2486
+ */
2487
+ function createPipelineValidationContext(pipeline) {
2488
+ return {
2489
+ pipeline,
2490
+ pipelineIdentification: getPipelineIdentification(pipeline),
2491
+ };
2492
+ }
2493
+ /**
2494
+ * Builds a short file/url identification block for validation errors.
2495
+ *
2496
+ * @private internal utility of `validatePipeline`
2497
+ */
2498
+ function getPipelineIdentification(pipeline) {
2499
+ // Note: This is a 😐 implementation of [🚞]
2500
+ const pipelineIdentificationParts = [];
2501
+ if (pipeline.sourceFile !== undefined) {
2502
+ pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
2503
+ }
2504
+ if (pipeline.pipelineUrl !== undefined) {
2505
+ pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
2506
+ }
2507
+ return pipelineIdentificationParts.join('\n');
2508
+ }
2509
+ /**
2510
+ * Validates pipeline-level metadata fields.
2511
+ *
2512
+ * @private internal step of `validatePipeline`
2513
+ */
2514
+ function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
2515
+ validatePipelineUrl(pipeline, pipelineIdentification);
2516
+ validatePipelineBookVersion(pipeline, pipelineIdentification);
2517
+ }
2518
+ /**
2519
+ * Validates that the expected top-level collections have array structure.
2520
+ *
2521
+ * @private internal step of `validatePipeline`
2522
+ */
2523
+ function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification }) {
2524
+ validatePipelineParametersCollection(pipeline, pipelineIdentification);
2525
+ validatePipelineTasksCollection(pipeline, pipelineIdentification);
2526
+ }
2527
+ /**
2528
+ * Validates all pipeline parameter declarations.
2529
+ *
2530
+ * @private internal step of `validatePipeline`
2531
+ */
2532
+ function validatePipelineParameters({ pipeline, pipelineIdentification }) {
2533
+ for (const parameter of pipeline.parameters) {
2534
+ validatePipelineParameter(parameter, pipeline, pipelineIdentification);
2535
+ }
2536
+ }
2537
+ /**
2538
+ * Validates all pipeline tasks and their per-task invariants.
2539
+ *
2540
+ * @private internal step of `validatePipeline`
2541
+ */
2542
+ function validatePipelineTasks({ pipeline, pipelineIdentification }) {
2543
+ // Note: All input parameters are defined - so that they can be used as result of some task
2544
+ const definedParameters = createInitiallyDefinedParameters(pipeline);
2545
+ for (const task of pipeline.tasks) {
2546
+ validatePipelineTask(task, definedParameters, pipelineIdentification);
2547
+ }
2548
+ }
2549
+ /**
2550
+ * Validates that task dependencies can be resolved without cycles or missing definitions.
2551
+ *
2552
+ * @private internal step of `validatePipeline`
2553
+ */
2554
+ function validatePipelineDependencyResolution({ pipeline, pipelineIdentification }) {
2555
+ let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
2556
+ let loopLimit = LOOP_LIMIT;
2557
+ while (hasUnresolvedTasks(dependencyResolutionState)) {
2558
+ if (loopLimit-- < 0) {
2559
+ throw createDependencyResolutionLoopLimitError(pipelineIdentification);
2356
2560
  }
2357
- return _.join('\n');
2358
- })();
2359
- if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
2360
- // <- Note: [🚲]
2361
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2362
- Invalid promptbook URL "${pipeline.pipelineUrl}"
2363
-
2364
- ${block(pipelineIdentification)}
2365
- `));
2561
+ dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
2366
2562
  }
2367
- if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
2368
- // <- Note: [🚲]
2369
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2370
- Invalid Promptbook Version "${pipeline.bookVersion}"
2563
+ }
2564
+ /**
2565
+ * Validates one pipeline parameter declaration.
2566
+ *
2567
+ * @private internal step of `validatePipeline`
2568
+ */
2569
+ function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
2570
+ validateParameterDirection(parameter, pipelineIdentification);
2571
+ validateParameterUsage(parameter, pipeline, pipelineIdentification);
2572
+ validateParameterDefinition(parameter, pipeline, pipelineIdentification);
2573
+ }
2574
+ /**
2575
+ * Validates one pipeline task and its invariants.
2576
+ *
2577
+ * @private internal step of `validatePipeline`
2578
+ */
2579
+ function validatePipelineTask(task, definedParameters, pipelineIdentification) {
2580
+ validateTaskResultingParameter(task, definedParameters, pipelineIdentification);
2581
+ validateTaskJokers(task, pipelineIdentification);
2582
+ validateTaskExpectations(task, pipelineIdentification);
2583
+ }
2584
+ /**
2585
+ * Validates the pipeline URL, when present.
2586
+ *
2587
+ * @private internal utility of `validatePipeline`
2588
+ */
2589
+ function validatePipelineUrl(pipeline, pipelineIdentification) {
2590
+ if (pipeline.pipelineUrl === undefined || isValidPipelineUrl(pipeline.pipelineUrl)) {
2591
+ return;
2592
+ }
2593
+ // <- Note: [🚲]
2594
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2595
+ Invalid promptbook URL "${pipeline.pipelineUrl}"
2371
2596
 
2372
- ${block(pipelineIdentification)}
2373
- `));
2597
+ ${block(pipelineIdentification)}
2598
+ `));
2599
+ }
2600
+ /**
2601
+ * Validates the Promptbook version, when present.
2602
+ *
2603
+ * @private internal utility of `validatePipeline`
2604
+ */
2605
+ function validatePipelineBookVersion(pipeline, pipelineIdentification) {
2606
+ if (pipeline.bookVersion === undefined || isValidPromptbookVersion(pipeline.bookVersion)) {
2607
+ return;
2374
2608
  }
2609
+ // <- Note: [🚲]
2610
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2611
+ Invalid Promptbook Version "${pipeline.bookVersion}"
2612
+
2613
+ ${block(pipelineIdentification)}
2614
+ `));
2615
+ }
2616
+ /**
2617
+ * Validates that `pipeline.parameters` is an array.
2618
+ *
2619
+ * @private internal utility of `validatePipeline`
2620
+ */
2621
+ function validatePipelineParametersCollection(pipeline, pipelineIdentification) {
2375
2622
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2376
- if (!Array.isArray(pipeline.parameters)) {
2377
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2378
- throw new ParseError(spacetrim.spaceTrim((block) => `
2379
- Pipeline is valid JSON but with wrong structure
2623
+ if (Array.isArray(pipeline.parameters)) {
2624
+ return;
2625
+ }
2626
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2627
+ throw new ParseError(spacetrim.spaceTrim((block) => `
2628
+ Pipeline is valid JSON but with wrong structure
2380
2629
 
2381
- \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2630
+ \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2382
2631
 
2383
- ${block(pipelineIdentification)}
2384
- `));
2385
- }
2632
+ ${block(pipelineIdentification)}
2633
+ `));
2634
+ }
2635
+ /**
2636
+ * Validates that `pipeline.tasks` is an array.
2637
+ *
2638
+ * @private internal utility of `validatePipeline`
2639
+ */
2640
+ function validatePipelineTasksCollection(pipeline, pipelineIdentification) {
2386
2641
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2387
- if (!Array.isArray(pipeline.tasks)) {
2388
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2389
- throw new ParseError(spacetrim.spaceTrim((block) => `
2390
- Pipeline is valid JSON but with wrong structure
2642
+ if (Array.isArray(pipeline.tasks)) {
2643
+ return;
2644
+ }
2645
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2646
+ throw new ParseError(spacetrim.spaceTrim((block) => `
2647
+ Pipeline is valid JSON but with wrong structure
2391
2648
 
2392
- \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2649
+ \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2393
2650
 
2394
- ${block(pipelineIdentification)}
2395
- `));
2651
+ ${block(pipelineIdentification)}
2652
+ `));
2653
+ }
2654
+ /**
2655
+ * Validates that one parameter does not declare incompatible directions.
2656
+ *
2657
+ * @private internal utility of `validatePipeline`
2658
+ */
2659
+ function validateParameterDirection(parameter, pipelineIdentification) {
2660
+ if (!parameter.isInput || !parameter.isOutput) {
2661
+ return;
2396
2662
  }
2397
- /*
2398
- TODO: [🧠][🅾] Should be empty pipeline valid or not
2399
- // Note: Check that pipeline has some tasks
2400
- if (pipeline.tasks.length === 0) {
2401
- throw new PipelineLogicError(
2402
- spaceTrim(
2403
- (block) => `
2404
- Pipeline must have at least one task
2663
+ const parameterName = parameter.name;
2664
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2405
2665
 
2406
- ${block(pipelineIdentification)}
2407
- `,
2408
- ),
2409
- );
2410
- }
2411
- */
2412
- // Note: Check each parameter individually
2413
- for (const parameter of pipeline.parameters) {
2414
- if (parameter.isInput && parameter.isOutput) {
2415
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2666
+ Parameter \`{${parameterName}}\` can not be both input and output
2416
2667
 
2417
- Parameter \`{${parameter.name}}\` can not be both input and output
2668
+ ${block(pipelineIdentification)}
2669
+ `));
2670
+ }
2671
+ /**
2672
+ * Validates that one intermediate parameter is actually consumed by at least one task.
2673
+ *
2674
+ * @private internal utility of `validatePipeline`
2675
+ */
2676
+ function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
2677
+ if (parameter.isInput || parameter.isOutput || isParameterUsedByAnyTask(parameter, pipeline.tasks)) {
2678
+ return;
2679
+ }
2680
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2681
+ Parameter \`{${parameter.name}}\` is created but not used
2418
2682
 
2419
- ${block(pipelineIdentification)}
2420
- `));
2421
- }
2422
- // Note: Testing that parameter is either intermediate or output BUT not created and unused
2423
- if (!parameter.isInput &&
2424
- !parameter.isOutput &&
2425
- !pipeline.tasks.some((task) => task.dependentParameterNames.includes(parameter.name))) {
2426
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2427
- Parameter \`{${parameter.name}}\` is created but not used
2683
+ You can declare {${parameter.name}} as output parameter by adding in the header:
2684
+ - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2428
2685
 
2429
- You can declare {${parameter.name}} as output parameter by adding in the header:
2430
- - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2686
+ ${block(pipelineIdentification)}
2431
2687
 
2432
- ${block(pipelineIdentification)}
2688
+ `));
2689
+ }
2690
+ /**
2691
+ * Validates that one non-input parameter is produced by at least one task.
2692
+ *
2693
+ * @private internal utility of `validatePipeline`
2694
+ */
2695
+ function validateParameterDefinition(parameter, pipeline, pipelineIdentification) {
2696
+ if (parameter.isInput || isParameterDefinedByAnyTask(parameter, pipeline.tasks)) {
2697
+ return;
2698
+ }
2699
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2700
+ Parameter \`{${parameter.name}}\` is declared but not defined
2433
2701
 
2434
- `));
2435
- }
2436
- // Note: Testing that parameter is either input or result of some task
2437
- if (!parameter.isInput && !pipeline.tasks.some((task) => task.resultingParameterName === parameter.name)) {
2438
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2439
- Parameter \`{${parameter.name}}\` is declared but not defined
2702
+ You can do one of these:
2703
+ 1) Remove declaration of \`{${parameter.name}}\`
2704
+ 2) Add task that results in \`-> {${parameter.name}}\`
2440
2705
 
2441
- You can do one of these:
2442
- 1) Remove declaration of \`{${parameter.name}}\`
2443
- 2) Add task that results in \`-> {${parameter.name}}\`
2706
+ ${block(pipelineIdentification)}
2707
+ `));
2708
+ }
2709
+ /**
2710
+ * Checks whether one parameter is consumed by at least one task dependency list.
2711
+ *
2712
+ * @private internal utility of `validatePipeline`
2713
+ */
2714
+ function isParameterUsedByAnyTask(parameter, tasks) {
2715
+ return tasks.some((task) => task.dependentParameterNames.includes(parameter.name));
2716
+ }
2717
+ /**
2718
+ * Checks whether one parameter is produced by at least one task.
2719
+ *
2720
+ * @private internal utility of `validatePipeline`
2721
+ */
2722
+ function isParameterDefinedByAnyTask(parameter, tasks) {
2723
+ return tasks.some((task) => task.resultingParameterName === parameter.name);
2724
+ }
2725
+ /**
2726
+ * Collects the parameter names that are already defined before task validation starts.
2727
+ *
2728
+ * @private internal utility of `validatePipeline`
2729
+ */
2730
+ function createInitiallyDefinedParameters(pipeline) {
2731
+ return new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2732
+ }
2733
+ /**
2734
+ * Validates one task result parameter declaration and marks it as defined.
2735
+ *
2736
+ * @private internal utility of `validatePipeline`
2737
+ */
2738
+ function validateTaskResultingParameter(task, definedParameters, pipelineIdentification) {
2739
+ if (definedParameters.has(task.resultingParameterName)) {
2740
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2741
+ Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2444
2742
 
2445
- ${block(pipelineIdentification)}
2446
- `));
2447
- }
2743
+ ${block(pipelineIdentification)}
2744
+ `));
2448
2745
  }
2449
- // Note: All input parameters are defined - so that they can be used as result of some task
2450
- const definedParameters = new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2451
- // Note: Checking each task individually
2452
- for (const task of pipeline.tasks) {
2453
- if (definedParameters.has(task.resultingParameterName)) {
2454
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2455
- Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2746
+ if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2747
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2748
+ Parameter name {${task.resultingParameterName}} is reserved, please use different name
2456
2749
 
2457
- ${block(pipelineIdentification)}
2458
- `));
2459
- }
2460
- if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2461
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2462
- Parameter name {${task.resultingParameterName}} is reserved, please use different name
2750
+ ${block(pipelineIdentification)}
2751
+ `));
2752
+ }
2753
+ definedParameters.add(task.resultingParameterName);
2754
+ }
2755
+ /**
2756
+ * Validates joker parameters for one task.
2757
+ *
2758
+ * @private internal utility of `validatePipeline`
2759
+ */
2760
+ function validateTaskJokers(task, pipelineIdentification) {
2761
+ if (!hasTaskJokers(task)) {
2762
+ return;
2763
+ }
2764
+ validateTaskSupportsJokers(task, pipelineIdentification);
2765
+ validateTaskJokerDependencies(task, pipelineIdentification);
2766
+ }
2767
+ /**
2768
+ * Checks whether one task declares any joker parameters.
2769
+ *
2770
+ * @private internal utility of `validatePipeline`
2771
+ */
2772
+ function hasTaskJokers(task) {
2773
+ return !!task.jokerParameterNames && task.jokerParameterNames.length > 0;
2774
+ }
2775
+ /**
2776
+ * Validates that a task has the required supporting features when using jokers.
2777
+ *
2778
+ * @private internal utility of `validatePipeline`
2779
+ */
2780
+ function validateTaskSupportsJokers(task, pipelineIdentification) {
2781
+ if (task.format ||
2782
+ task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2783
+ return;
2784
+ }
2785
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2786
+ Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2463
2787
 
2464
- ${block(pipelineIdentification)}
2465
- `));
2788
+ ${block(pipelineIdentification)}
2789
+ `));
2790
+ }
2791
+ /**
2792
+ * Validates that every joker parameter is also listed among task dependencies.
2793
+ *
2794
+ * @private internal utility of `validatePipeline`
2795
+ */
2796
+ function validateTaskJokerDependencies(task, pipelineIdentification) {
2797
+ for (const joker of task.jokerParameterNames) {
2798
+ if (task.dependentParameterNames.includes(joker)) {
2799
+ continue;
2466
2800
  }
2467
- definedParameters.add(task.resultingParameterName);
2468
- if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
2469
- if (!task.format &&
2470
- !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2471
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2472
- Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2473
-
2474
- ${block(pipelineIdentification)}
2475
- `));
2476
- }
2477
- for (const joker of task.jokerParameterNames) {
2478
- if (!task.dependentParameterNames.includes(joker)) {
2479
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2480
- Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2801
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2802
+ Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2481
2803
 
2482
- ${block(pipelineIdentification)}
2483
- `));
2484
- }
2485
- }
2486
- }
2487
- if (task.expectations) {
2488
- for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2489
- if (min !== undefined && max !== undefined && min > max) {
2490
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2491
- Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2804
+ ${block(pipelineIdentification)}
2805
+ `));
2806
+ }
2807
+ }
2808
+ /**
2809
+ * Validates all expectation bounds configured on one task.
2810
+ *
2811
+ * @private internal utility of `validatePipeline`
2812
+ */
2813
+ function validateTaskExpectations(task, pipelineIdentification) {
2814
+ if (!task.expectations) {
2815
+ return;
2816
+ }
2817
+ for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2818
+ validateTaskExpectationRange(unit, min, max, pipelineIdentification);
2819
+ validateTaskExpectationMin(unit, min, pipelineIdentification);
2820
+ validateTaskExpectationMax(unit, max, pipelineIdentification);
2821
+ }
2822
+ }
2823
+ /**
2824
+ * Validates the minimum and maximum expectation ordering for one unit.
2825
+ *
2826
+ * @private internal utility of `validatePipeline`
2827
+ */
2828
+ function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
2829
+ if (min === undefined || max === undefined || min <= max) {
2830
+ return;
2831
+ }
2832
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2833
+ Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2492
2834
 
2493
- ${block(pipelineIdentification)}
2494
- `));
2495
- }
2496
- if (min !== undefined && min < 0) {
2497
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2498
- Min expectation of ${unit} must be zero or positive
2835
+ ${block(pipelineIdentification)}
2836
+ `));
2837
+ }
2838
+ /**
2839
+ * Validates the minimum expectation bound for one unit.
2840
+ *
2841
+ * @private internal utility of `validatePipeline`
2842
+ */
2843
+ function validateTaskExpectationMin(unit, min, pipelineIdentification) {
2844
+ if (min === undefined || min >= 0) {
2845
+ return;
2846
+ }
2847
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2848
+ Min expectation of ${unit} must be zero or positive
2499
2849
 
2500
- ${block(pipelineIdentification)}
2501
- `));
2502
- }
2503
- if (max !== undefined && max <= 0) {
2504
- throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2505
- Max expectation of ${unit} must be positive
2850
+ ${block(pipelineIdentification)}
2851
+ `));
2852
+ }
2853
+ /**
2854
+ * Validates the maximum expectation bound for one unit.
2855
+ *
2856
+ * @private internal utility of `validatePipeline`
2857
+ */
2858
+ function validateTaskExpectationMax(unit, max, pipelineIdentification) {
2859
+ if (max === undefined || max > 0) {
2860
+ return;
2861
+ }
2862
+ throw new PipelineLogicError(spacetrim.spaceTrim((block) => `
2863
+ Max expectation of ${unit} must be positive
2506
2864
 
2507
- ${block(pipelineIdentification)}
2508
- `));
2509
- }
2510
- }
2511
- }
2865
+ ${block(pipelineIdentification)}
2866
+ `));
2867
+ }
2868
+ /**
2869
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
2870
+ *
2871
+ * @private internal utility of `validatePipeline`
2872
+ */
2873
+ function createInitialDependencyResolutionState(pipeline) {
2874
+ return {
2875
+ resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
2876
+ unresolvedTasks: [...pipeline.tasks],
2877
+ };
2878
+ }
2879
+ /**
2880
+ * Checks whether dependency resolution still has tasks left to process.
2881
+ *
2882
+ * @private internal utility of `validatePipeline`
2883
+ */
2884
+ function hasUnresolvedTasks({ unresolvedTasks }) {
2885
+ return unresolvedTasks.length > 0;
2886
+ }
2887
+ /**
2888
+ * Resolves the next batch of currently satisfiable tasks.
2889
+ *
2890
+ * @private internal utility of `validatePipeline`
2891
+ */
2892
+ function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
2893
+ const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
2894
+ if (currentlyResolvedTasks.length === 0) {
2895
+ throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
2512
2896
  }
2513
- // Note: Detect circular dependencies
2514
- let resovedParameters = pipeline.parameters
2897
+ return {
2898
+ resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
2899
+ unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
2900
+ };
2901
+ }
2902
+ /**
2903
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
2904
+ *
2905
+ * @private internal utility of `validatePipeline`
2906
+ */
2907
+ function createInitiallyResolvedParameterNames(pipeline) {
2908
+ let resolvedParameterNames = pipeline.parameters
2515
2909
  .filter(({ isInput }) => isInput)
2516
2910
  .map(({ name }) => name);
2517
- // Note: All reserved parameters are resolved
2518
2911
  for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
2519
- resovedParameters = [...resovedParameters, reservedParameterName];
2912
+ resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
2520
2913
  }
2521
- let unresovedTasks = [...pipeline.tasks];
2522
- let loopLimit = LOOP_LIMIT;
2523
- while (unresovedTasks.length > 0) {
2524
- if (loopLimit-- < 0) {
2525
- // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2526
- throw new UnexpectedError(spacetrim.spaceTrim((block) => `
2527
- Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2914
+ return resolvedParameterNames;
2915
+ }
2916
+ /**
2917
+ * Adds newly resolved task outputs to the resolved parameter list.
2918
+ *
2919
+ * @private internal utility of `validatePipeline`
2920
+ */
2921
+ function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
2922
+ return [
2923
+ ...resolvedParameterNames,
2924
+ ...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
2925
+ ];
2926
+ }
2927
+ /**
2928
+ * Selects tasks whose dependencies are already resolved.
2929
+ *
2930
+ * @private internal utility of `validatePipeline`
2931
+ */
2932
+ function getCurrentlyResolvedTasks(unresolvedTasks, resolvedParameterNames) {
2933
+ return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
2934
+ }
2935
+ /**
2936
+ * Creates the unexpected loop-limit error for dependency resolution.
2937
+ *
2938
+ * @private internal utility of `validatePipeline`
2939
+ */
2940
+ function createDependencyResolutionLoopLimitError(pipelineIdentification) {
2941
+ // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2942
+ return new UnexpectedError(spacetrim.spaceTrim((block) => `
2943
+ Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2528
2944
 
2529
- ${block(pipelineIdentification)}
2530
- `));
2531
- }
2532
- const currentlyResovedTasks = unresovedTasks.filter((task) => task.dependentParameterNames.every((name) => resovedParameters.includes(name)));
2533
- if (currentlyResovedTasks.length === 0) {
2534
- throw new PipelineLogicError(
2535
- // TODO: [🐎] DRY
2536
- spacetrim.spaceTrim((block) => `
2945
+ ${block(pipelineIdentification)}
2946
+ `));
2947
+ }
2948
+ /**
2949
+ * Creates the detailed error for unresolved or circular task dependencies.
2950
+ *
2951
+ * @private internal utility of `validatePipeline`
2952
+ */
2953
+ function createUnresolvedTasksError(unresolvedTasks, resolvedParameterNames, pipelineIdentification) {
2954
+ return new PipelineLogicError(
2955
+ // TODO: [🐎] DRY
2956
+ spacetrim.spaceTrim((block) => `
2537
2957
 
2538
- Can not resolve some parameters:
2539
- Either you are using a parameter that is not defined, or there are some circular dependencies.
2958
+ Can not resolve some parameters:
2959
+ Either you are using a parameter that is not defined, or there are some circular dependencies.
2540
2960
 
2541
- ${block(pipelineIdentification)}
2961
+ ${block(pipelineIdentification)}
2542
2962
 
2543
- **Can not resolve:**
2544
- ${block(unresovedTasks
2545
- .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2546
- .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2547
- .join(' and ')}`)
2548
- .join('\n'))}
2963
+ **Can not resolve:**
2964
+ ${block(unresolvedTasks
2965
+ .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2966
+ .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2967
+ .join(' and ')}`)
2968
+ .join('\n'))}
2549
2969
 
2550
- **Resolved:**
2551
- ${block(resovedParameters
2552
- .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2553
- .map((name) => `- Parameter \`{${name}}\``)
2554
- .join('\n'))}
2970
+ **Resolved:**
2971
+ ${block(resolvedParameterNames
2972
+ .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2973
+ .map((name) => `- Parameter \`{${name}}\``)
2974
+ .join('\n'))}
2555
2975
 
2556
2976
 
2557
- **Reserved (which are available):**
2558
- ${block(resovedParameters
2559
- .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2560
- .map((name) => `- Parameter \`{${name}}\``)
2561
- .join('\n'))}
2977
+ **Reserved (which are available):**
2978
+ ${block(resolvedParameterNames
2979
+ .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2980
+ .map((name) => `- Parameter \`{${name}}\``)
2981
+ .join('\n'))}
2562
2982
 
2563
2983
 
2564
- `));
2565
- }
2566
- resovedParameters = [
2567
- ...resovedParameters,
2568
- ...currentlyResovedTasks.map(({ resultingParameterName }) => resultingParameterName),
2569
- ];
2570
- unresovedTasks = unresovedTasks.filter((task) => !currentlyResovedTasks.includes(task));
2571
- }
2572
- // Note: Check that formfactor is corresponding to the pipeline interface
2573
- // TODO: !!6 Implement this
2574
- // pipeline.formfactorName
2984
+ `));
2575
2985
  }
2576
2986
  /**
2577
2987
  * TODO: [🧞‍♀️] Do not allow joker + foreach
@@ -3244,76 +3654,287 @@
3244
3654
  // TODO: [🧠] Can this return type be better typed than void
3245
3655
 
3246
3656
  /**
3247
- * Helper to create a new task
3657
+ * Resolves the short task summary shown in the UI.
3248
3658
  *
3249
- * @private internal helper function
3659
+ * @private internal helper function of `ExecutionTask`
3250
3660
  */
3251
- function createTask(options) {
3252
- const { taskType, taskProcessCallback } = options;
3253
- let { title } = options;
3254
- // TODO: [🐙] DRY
3255
- const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3256
- let status = 'RUNNING';
3257
- const createdAt = new Date();
3258
- let updatedAt = createdAt;
3259
- const errors = [];
3260
- const warnings = [];
3261
- const llmCalls = [];
3262
- let currentValue = {};
3263
- let customTldr = null;
3264
- const partialResultSubject = new rxjs.Subject();
3265
- // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3266
- const finalResultPromise = /* not await */ taskProcessCallback((newOngoingResult) => {
3267
- if (newOngoingResult.title) {
3268
- title = newOngoingResult.title;
3269
- }
3270
- updatedAt = new Date();
3271
- Object.assign(currentValue, newOngoingResult);
3272
- // <- TODO: assign deep
3273
- partialResultSubject.next(newOngoingResult);
3274
- }, (tldrInfo) => {
3275
- customTldr = tldrInfo;
3276
- updatedAt = new Date();
3277
- }, (llmCall) => {
3278
- llmCalls.push(llmCall);
3279
- updatedAt = new Date();
3280
- });
3281
- finalResultPromise
3282
- .catch((error) => {
3283
- errors.push(error);
3284
- partialResultSubject.error(error);
3285
- })
3286
- .then((executionResult) => {
3287
- if (executionResult) {
3288
- try {
3289
- updatedAt = new Date();
3290
- errors.push(...executionResult.errors);
3291
- warnings.push(...executionResult.warnings);
3292
- // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3293
- // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3294
- // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3295
- // And delete `ExecutionTask.currentValue.preparedPipeline`
3296
- assertsTaskSuccessful(executionResult);
3297
- status = 'FINISHED';
3298
- currentValue = jsonStringsToJsons(executionResult);
3299
- // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3300
- partialResultSubject.next(executionResult);
3301
- }
3302
- catch (error) {
3303
- assertsError(error);
3304
- status = 'ERROR';
3305
- errors.push(error);
3306
- partialResultSubject.error(error);
3307
- }
3308
- }
3309
- partialResultSubject.complete();
3310
- });
3311
- async function asPromise(options) {
3312
- const { isCrashedOnError = true } = options || {};
3313
- const finalResult = await finalResultPromise;
3314
- if (isCrashedOnError) {
3315
- assertsTaskSuccessful(finalResult);
3316
- }
3661
+ function resolveTaskTldr(options) {
3662
+ const { customTldr } = options;
3663
+ if (customTldr) {
3664
+ return customTldr;
3665
+ }
3666
+ return {
3667
+ percent: resolveTaskPercent(options),
3668
+ message: `${resolveTaskMessage(options)} (!!!fallback)`,
3669
+ };
3670
+ }
3671
+ /**
3672
+ * Resolves the best progress percentage for the current task state.
3673
+ *
3674
+ * @private internal helper function of `ExecutionTask`
3675
+ */
3676
+ function resolveTaskPercent(options) {
3677
+ const explicitPercent = getExplicitTaskPercent(options.currentValue);
3678
+ if (typeof explicitPercent === 'number') {
3679
+ return normalizeTaskPercent(explicitPercent);
3680
+ }
3681
+ return normalizeTaskPercent(calculateSimulatedTaskPercent(options));
3682
+ }
3683
+ /**
3684
+ * Picks a directly reported progress percentage from the task result snapshot.
3685
+ *
3686
+ * @private internal helper function of `ExecutionTask`
3687
+ */
3688
+ function getExplicitTaskPercent(currentValue) {
3689
+ var _a, _b, _c, _d, _e, _f;
3690
+ 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);
3691
+ }
3692
+ /**
3693
+ * Simulates progress when the task result does not expose an explicit percentage.
3694
+ *
3695
+ * @private internal helper function of `ExecutionTask`
3696
+ */
3697
+ function calculateSimulatedTaskPercent(options) {
3698
+ const { currentValue, status, createdAt } = options;
3699
+ const elapsedMs = new Date().getTime() - createdAt.getTime();
3700
+ const timeProgress = Math.min(elapsedMs / DEFAULT_TASK_SIMULATED_DURATION_MS, 1);
3701
+ const { subtaskCount, completedSubtasks } = summarizeTaskSubtasks(currentValue);
3702
+ if (status === 'FINISHED') {
3703
+ return 1;
3704
+ }
3705
+ if (status === 'ERROR') {
3706
+ return 0;
3707
+ }
3708
+ return Math.min(completedSubtasks / subtaskCount + (1 / subtaskCount) * timeProgress, 1);
3709
+ }
3710
+ /**
3711
+ * Counts total and completed subtasks used by the fallback progress simulation.
3712
+ *
3713
+ * @private internal helper function of `ExecutionTask`
3714
+ */
3715
+ function summarizeTaskSubtasks(currentValue) {
3716
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks)) {
3717
+ return { subtaskCount: 1, completedSubtasks: 0 };
3718
+ }
3719
+ return {
3720
+ subtaskCount: currentValue.subtasks.length || 1,
3721
+ completedSubtasks: currentValue.subtasks.filter(isTaskSubtaskCompleted).length,
3722
+ };
3723
+ }
3724
+ /**
3725
+ * Tells whether a task subtask is already finished.
3726
+ *
3727
+ * @private internal helper function of `ExecutionTask`
3728
+ */
3729
+ function isTaskSubtaskCompleted(subtask) {
3730
+ return subtask.done || subtask.completed || false;
3731
+ }
3732
+ /**
3733
+ * Normalizes a progress percentage into the expected `0..1` range.
3734
+ *
3735
+ * @private internal helper function of `ExecutionTask`
3736
+ */
3737
+ function normalizeTaskPercent(percentRaw) {
3738
+ let percent = Number(percentRaw) || 0;
3739
+ if (percent < 0) {
3740
+ percent = 0;
3741
+ }
3742
+ if (percent > 1) {
3743
+ percent = 1;
3744
+ }
3745
+ return percent;
3746
+ }
3747
+ /**
3748
+ * Resolves the best human-readable status message for the current task state.
3749
+ *
3750
+ * @private internal helper function of `ExecutionTask`
3751
+ */
3752
+ function resolveTaskMessage(options) {
3753
+ return (getCurrentValueMessage(options.currentValue) ||
3754
+ getCurrentSubtaskMessage(options.currentValue) ||
3755
+ getLatestIssueMessage(options.errors, 'Error') ||
3756
+ getLatestIssueMessage(options.warnings, 'Warning') ||
3757
+ getStatusMessage(options.status));
3758
+ }
3759
+ /**
3760
+ * Picks a message already reported by the current task result snapshot.
3761
+ *
3762
+ * @private internal helper function of `ExecutionTask`
3763
+ */
3764
+ function getCurrentValueMessage(currentValue) {
3765
+ var _a, _b, _c, _d;
3766
+ 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;
3767
+ }
3768
+ /**
3769
+ * Builds a fallback message from the first unfinished subtask title.
3770
+ *
3771
+ * @private internal helper function of `ExecutionTask`
3772
+ */
3773
+ function getCurrentSubtaskMessage(currentValue) {
3774
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks) || currentValue.subtasks.length === 0) {
3775
+ return undefined;
3776
+ }
3777
+ const currentSubtask = currentValue.subtasks.find((subtask) => !isTaskSubtaskCompleted(subtask));
3778
+ if (!(currentSubtask === null || currentSubtask === void 0 ? void 0 : currentSubtask.title)) {
3779
+ return undefined;
3780
+ }
3781
+ return `Working on ${currentSubtask.title}`;
3782
+ }
3783
+ /**
3784
+ * Picks the latest error or warning message, with the legacy generic fallback label.
3785
+ *
3786
+ * @private internal helper function of `ExecutionTask`
3787
+ */
3788
+ function getLatestIssueMessage(issues, fallbackMessage) {
3789
+ if (issues.length === 0) {
3790
+ return undefined;
3791
+ }
3792
+ return issues[issues.length - 1].message || fallbackMessage;
3793
+ }
3794
+ /**
3795
+ * Builds the final status-based fallback message.
3796
+ *
3797
+ * @private internal helper function of `ExecutionTask`
3798
+ */
3799
+ function getStatusMessage(status) {
3800
+ if (status === 'FINISHED') {
3801
+ return 'Finished';
3802
+ }
3803
+ if (status === 'ERROR') {
3804
+ return 'Error';
3805
+ }
3806
+ return 'Running';
3807
+ }
3808
+
3809
+ /**
3810
+ * Creates the initial mutable state for a task.
3811
+ *
3812
+ * @private internal helper function
3813
+ */
3814
+ function createTaskState(title, createdAt) {
3815
+ return {
3816
+ title,
3817
+ status: 'RUNNING',
3818
+ updatedAt: createdAt,
3819
+ errors: [],
3820
+ warnings: [],
3821
+ llmCalls: [],
3822
+ currentValue: {},
3823
+ customTldr: null,
3824
+ };
3825
+ }
3826
+ /**
3827
+ * Creates the partial-result updater passed into the task process callback.
3828
+ *
3829
+ * @private internal helper function
3830
+ */
3831
+ function createOngoingResultUpdater(taskState, partialResultSubject) {
3832
+ return (newOngoingResult) => {
3833
+ if (newOngoingResult.title) {
3834
+ taskState.title = newOngoingResult.title;
3835
+ }
3836
+ taskState.updatedAt = new Date();
3837
+ Object.assign(taskState.currentValue, newOngoingResult);
3838
+ // <- TODO: assign deep
3839
+ partialResultSubject.next(newOngoingResult);
3840
+ };
3841
+ }
3842
+ /**
3843
+ * Creates the custom-TLDR updater passed into the task process callback.
3844
+ *
3845
+ * @private internal helper function
3846
+ */
3847
+ function createTldrUpdater(taskState) {
3848
+ return (tldrInfo) => {
3849
+ taskState.customTldr = tldrInfo;
3850
+ taskState.updatedAt = new Date();
3851
+ };
3852
+ }
3853
+ /**
3854
+ * Creates the LLM call logger passed into the task process callback.
3855
+ *
3856
+ * @private internal helper function
3857
+ */
3858
+ function createLlmCallLogger(taskState) {
3859
+ return (llmCall) => {
3860
+ taskState.llmCalls.push(llmCall);
3861
+ taskState.updatedAt = new Date();
3862
+ };
3863
+ }
3864
+ /**
3865
+ * Wires the task promise into the observable/error lifecycle.
3866
+ *
3867
+ * @private internal helper function
3868
+ */
3869
+ function settleTaskPromise(finalResultPromise, taskState, partialResultSubject) {
3870
+ finalResultPromise
3871
+ .catch((error) => {
3872
+ taskState.errors.push(error);
3873
+ partialResultSubject.error(error);
3874
+ })
3875
+ .then((executionResult) => {
3876
+ if (executionResult) {
3877
+ try {
3878
+ finalizeTaskResult(executionResult, taskState, partialResultSubject);
3879
+ }
3880
+ catch (error) {
3881
+ failTaskResult(error, taskState, partialResultSubject);
3882
+ }
3883
+ }
3884
+ partialResultSubject.complete();
3885
+ });
3886
+ }
3887
+ /**
3888
+ * Applies the final successful task result into the mutable task state.
3889
+ *
3890
+ * @private internal helper function
3891
+ */
3892
+ function finalizeTaskResult(executionResult, taskState, partialResultSubject) {
3893
+ taskState.updatedAt = new Date();
3894
+ taskState.errors.push(...executionResult.errors);
3895
+ taskState.warnings.push(...executionResult.warnings);
3896
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3897
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3898
+ // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3899
+ // And delete `ExecutionTask.currentValue.preparedPipeline`
3900
+ assertsTaskSuccessful(executionResult);
3901
+ taskState.status = 'FINISHED';
3902
+ taskState.currentValue = jsonStringsToJsons(executionResult);
3903
+ // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3904
+ partialResultSubject.next(executionResult);
3905
+ }
3906
+ /**
3907
+ * Records a final-result failure after the task promise itself resolved.
3908
+ *
3909
+ * @private internal helper function
3910
+ */
3911
+ function failTaskResult(error, taskState, partialResultSubject) {
3912
+ assertsError(error);
3913
+ taskState.status = 'ERROR';
3914
+ taskState.errors.push(error);
3915
+ partialResultSubject.error(error);
3916
+ }
3917
+ /**
3918
+ * Helper to create a new task
3919
+ *
3920
+ * @private internal helper function
3921
+ */
3922
+ function createTask(options) {
3923
+ const { taskType, title, taskProcessCallback } = options;
3924
+ // TODO: [🐙] DRY
3925
+ const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3926
+ const createdAt = new Date();
3927
+ const taskState = createTaskState(title, createdAt);
3928
+ const partialResultSubject = new rxjs.Subject();
3929
+ // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3930
+ const finalResultPromise = /* not await */ taskProcessCallback(createOngoingResultUpdater(taskState, partialResultSubject), createTldrUpdater(taskState), createLlmCallLogger(taskState));
3931
+ settleTaskPromise(finalResultPromise, taskState, partialResultSubject);
3932
+ async function asPromise(options) {
3933
+ const { isCrashedOnError = true } = options || {};
3934
+ const finalResult = await finalResultPromise;
3935
+ if (isCrashedOnError) {
3936
+ assertsTaskSuccessful(finalResult);
3937
+ }
3317
3938
  return finalResult;
3318
3939
  }
3319
3940
  return {
@@ -3323,91 +3944,29 @@
3323
3944
  return PROMPTBOOK_ENGINE_VERSION;
3324
3945
  },
3325
3946
  get title() {
3326
- return title;
3947
+ return taskState.title;
3327
3948
  // <- Note: [1] These must be getters to allow changing the value in the future
3328
3949
  },
3329
3950
  get status() {
3330
- return status;
3951
+ return taskState.status;
3331
3952
  // <- Note: [1] --||--
3332
3953
  },
3333
3954
  get tldr() {
3334
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3335
- // Use custom tldr if available
3336
- if (customTldr) {
3337
- return customTldr;
3338
- }
3339
- // Fallback to default implementation
3340
- const cv = currentValue;
3341
- // If explicit percent is provided, use it
3342
- 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;
3343
- // Simulate progress if not provided
3344
- if (typeof percentRaw !== 'number') {
3345
- // Simulate progress: evenly split across subtasks, based on elapsed time
3346
- const now = new Date();
3347
- const elapsedMs = now.getTime() - createdAt.getTime();
3348
- const totalMs = DEFAULT_TASK_SIMULATED_DURATION_MS;
3349
- // If subtasks are defined, split progress evenly
3350
- const subtaskCount = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) ? cv.subtasks.length : 1;
3351
- const completedSubtasks = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks)
3352
- ? cv.subtasks.filter((s) => s.done || s.completed).length
3353
- : 0;
3354
- // Progress from completed subtasks
3355
- const subtaskProgress = subtaskCount > 0 ? completedSubtasks / subtaskCount : 0;
3356
- // Progress from elapsed time for current subtask
3357
- const timeProgress = Math.min(elapsedMs / totalMs, 1);
3358
- // Combine: completed subtasks + time progress for current subtask
3359
- percentRaw = Math.min(subtaskProgress + (1 / subtaskCount) * timeProgress, 1);
3360
- if (status === 'FINISHED')
3361
- percentRaw = 1;
3362
- if (status === 'ERROR')
3363
- percentRaw = 0;
3364
- }
3365
- // Clamp to [0,1]
3366
- let percent = Number(percentRaw) || 0;
3367
- if (percent < 0)
3368
- percent = 0;
3369
- if (percent > 1)
3370
- percent = 1;
3371
- // Build a short message: prefer explicit tldr.message, then common summary/message fields, then errors/warnings, then status
3372
- 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;
3373
- let message = messageFromResult;
3374
- if (!message) {
3375
- // If subtasks, show current subtask
3376
- if (Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) && cv.subtasks.length > 0) {
3377
- const current = cv.subtasks.find((s) => !s.done && !s.completed);
3378
- if (current && current.title) {
3379
- message = `Working on ${current.title}`;
3380
- }
3381
- }
3382
- if (!message) {
3383
- if (errors.length) {
3384
- message = errors[errors.length - 1].message || 'Error';
3385
- }
3386
- else if (warnings.length) {
3387
- message = warnings[warnings.length - 1].message || 'Warning';
3388
- }
3389
- else if (status === 'FINISHED') {
3390
- message = 'Finished';
3391
- }
3392
- else if (status === 'ERROR') {
3393
- message = 'Error';
3394
- }
3395
- else {
3396
- message = 'Running';
3397
- }
3398
- }
3399
- }
3400
- return {
3401
- percent: percent,
3402
- message: message + ' (!!!fallback)',
3403
- };
3955
+ return resolveTaskTldr({
3956
+ customTldr: taskState.customTldr,
3957
+ currentValue: taskState.currentValue,
3958
+ status: taskState.status,
3959
+ createdAt,
3960
+ errors: taskState.errors,
3961
+ warnings: taskState.warnings,
3962
+ });
3404
3963
  },
3405
3964
  get createdAt() {
3406
3965
  return createdAt;
3407
3966
  // <- Note: [1] --||--
3408
3967
  },
3409
3968
  get updatedAt() {
3410
- return updatedAt;
3969
+ return taskState.updatedAt;
3411
3970
  // <- Note: [1] --||--
3412
3971
  },
3413
3972
  asPromise,
@@ -3415,19 +3974,19 @@
3415
3974
  return partialResultSubject.asObservable();
3416
3975
  },
3417
3976
  get errors() {
3418
- return errors;
3977
+ return taskState.errors;
3419
3978
  // <- Note: [1] --||--
3420
3979
  },
3421
3980
  get warnings() {
3422
- return warnings;
3981
+ return taskState.warnings;
3423
3982
  // <- Note: [1] --||--
3424
3983
  },
3425
3984
  get llmCalls() {
3426
- return [...llmCalls, { foo: '!!! bar' }];
3985
+ return [...taskState.llmCalls, { foo: '!!! bar' }];
3427
3986
  // <- Note: [1] --||--
3428
3987
  },
3429
3988
  get currentValue() {
3430
- return currentValue;
3989
+ return taskState.currentValue;
3431
3990
  // <- Note: [1] --||--
3432
3991
  },
3433
3992
  };
@@ -4595,223 +5154,288 @@
4595
5154
  * @public exported from `@promptbook/core`
4596
5155
  */
4597
5156
  async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
4598
- var _a;
4599
- const { fetch = promptbookFetch } = tools;
4600
5157
  const { knowledgeSourceContent } = knowledgeSource;
4601
- let { name } = knowledgeSource;
4602
- const { rootDirname = null,
4603
- // <- TODO: process.cwd() if running in Node.js
4604
- isVerbose = DEFAULT_IS_VERBOSE, } = options || {};
4605
- if (!name) {
4606
- name = knowledgeSourceContentToName(knowledgeSourceContent);
4607
- }
5158
+ const name = knowledgeSource.name || knowledgeSourceContentToName(knowledgeSourceContent);
5159
+ const { rootDirname = null, isVerbose = DEFAULT_IS_VERBOSE } = options || {};
4608
5160
  if (isValidUrl(knowledgeSourceContent)) {
4609
- const url = knowledgeSourceContent;
4610
- if (isVerbose) {
4611
- console.info(`📄 [1] "${name}" is available at "${url}"`);
4612
- }
4613
- const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
4614
- const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
4615
- if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
4616
- if (isVerbose) {
4617
- console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
4618
- }
4619
- return {
4620
- source: name,
4621
- filename: null,
4622
- url,
4623
- mimeType,
4624
- /*
4625
- TODO: [🥽]
4626
- > async asBlob() {
4627
- > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
4628
- > const content = await response.blob();
4629
- > return content;
4630
- > },
4631
- */
4632
- async asJson() {
4633
- // TODO: [👨🏻‍🤝‍👨🏻]
4634
- const content = await response.json();
4635
- return content;
4636
- },
4637
- async asText() {
4638
- // TODO: [👨🏻‍🤝‍👨🏻]
4639
- const content = await response.text();
4640
- return content;
4641
- },
4642
- };
4643
- }
4644
- const basename = url.split('/').pop() || titleToName(url);
4645
- const hash = sha256__default["default"](hexEncoder__default["default"].parse(url)).toString( /* hex */);
4646
- // <- TODO: [🥬] Encapsulate sha256 to some private utility function
4647
- const rootDirname = path.join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
4648
- const filepath = path.join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
4649
- // Note: Try to create cache directory, but don't fail if filesystem has issues
4650
- try {
4651
- await tools.fs.mkdir(path.dirname(path.join(rootDirname, filepath)), { recursive: true });
4652
- }
4653
- catch (error) {
4654
- if (isVerbose) {
4655
- console.info(`📄 [3] "${name}" error creating cache directory`);
4656
- }
4657
- // Note: If we can't create cache directory, we'll handle it when trying to write the file
4658
- // This handles read-only filesystems, permission issues, and missing parent directories
4659
- if (error instanceof Error &&
4660
- (error.message.includes('EROFS') ||
4661
- error.message.includes('read-only') ||
4662
- error.message.includes('EACCES') ||
4663
- error.message.includes('EPERM') ||
4664
- error.message.includes('ENOENT'))) ;
4665
- else {
4666
- // Re-throw other unexpected errors
4667
- throw error;
4668
- }
4669
- }
4670
- const fileContent = Buffer.from(await response.arrayBuffer());
4671
- if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
4672
- 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.`);
4673
- }
4674
- // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
4675
- try {
4676
- await tools.fs.writeFile(path.join(rootDirname, filepath), fileContent);
4677
- }
4678
- catch (error) {
4679
- if (isVerbose) {
4680
- console.info(`📄 [4] "${name}" error writing cache file`);
4681
- }
4682
- // Note: If we can't write to cache, we'll process the file directly from memory
4683
- // This handles read-only filesystems like Vercel
4684
- if (error instanceof Error &&
4685
- (error.message.includes('EROFS') ||
4686
- error.message.includes('read-only') ||
4687
- error.message.includes('EACCES') ||
4688
- error.message.includes('EPERM') ||
4689
- error.message.includes('ENOENT'))) {
4690
- // Return a handler that works directly with the downloaded content
4691
- return {
4692
- source: name,
4693
- filename: null,
4694
- url,
4695
- mimeType,
4696
- async asJson() {
4697
- return JSON.parse(fileContent.toString('utf-8'));
4698
- },
4699
- async asText() {
4700
- return fileContent.toString('utf-8');
4701
- },
4702
- };
4703
- }
4704
- else {
4705
- // Re-throw other unexpected errors
4706
- throw error;
4707
- }
4708
- }
4709
- // TODO: [💵] Check the file security
4710
- // TODO: [🧹][🧠] Delete the file after the scraping is done
4711
- if (isVerbose) {
4712
- console.info(`📄 [5] "${name}" cached at "${path.join(rootDirname, filepath)}"`);
4713
- }
4714
- return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
4715
- ...options,
4716
- rootDirname,
4717
- });
4718
- }
4719
- else if (isValidFilePath(knowledgeSourceContent)) {
4720
- if (tools.fs === undefined) {
4721
- throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
4722
- // <- TODO: [🧠] What is the best error type here`
4723
- }
4724
- if (rootDirname === null) {
4725
- throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
4726
- // <- TODO: [🧠] What is the best error type here`
4727
- }
4728
- const filename = path.isAbsolute(knowledgeSourceContent)
4729
- ? knowledgeSourceContent
4730
- : path.join(rootDirname, knowledgeSourceContent).split('\\').join('/');
4731
- if (isVerbose) {
4732
- console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
4733
- }
4734
- const fileExtension = getFileExtension(filename);
4735
- const mimeType = extensionToMimeType(fileExtension || '');
4736
- if (!(await isFileExisting(filename, tools.fs))) {
4737
- throw new NotFoundError(spacetrim.spaceTrim((block) => `
4738
- Can not make source handler for file which does not exist:
4739
-
4740
- File:
4741
- ${block(knowledgeSourceContent)}
4742
-
4743
- Full file path:
4744
- ${block(filename)}
4745
- `));
4746
- }
4747
- // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
4748
- return {
4749
- source: name,
4750
- filename,
4751
- url: null,
4752
- mimeType,
4753
- /*
4754
- TODO: [🥽]
4755
- > async asBlob() {
4756
- > const content = await tools.fs!.readFile(filename);
4757
- > return new Blob(
4758
- > [
4759
- > content,
4760
- > // <- TODO: [🥽] This is NOT tested, test it
4761
- > ],
4762
- > { type: mimeType },
4763
- > );
4764
- > },
4765
- */
4766
- async asJson() {
4767
- return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
4768
- },
4769
- async asText() {
4770
- return await tools.fs.readFile(filename, 'utf-8');
4771
- },
4772
- };
5161
+ return makeUrlKnowledgeSourceHandler(knowledgeSourceContent, name, tools, options, isVerbose);
4773
5162
  }
4774
- else {
4775
- if (isVerbose) {
4776
- console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
4777
- console.info('---');
4778
- console.info(knowledgeSourceContent);
4779
- console.info('---');
4780
- }
4781
- return {
4782
- source: name,
4783
- filename: null,
4784
- url: null,
4785
- mimeType: 'text/markdown',
4786
- asText() {
4787
- return knowledgeSource.knowledgeSourceContent;
4788
- },
4789
- asJson() {
4790
- throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
4791
- },
4792
- /*
4793
- TODO: [🥽]
4794
- > asBlob() {
4795
- > throw new UnexpectedError(
4796
- > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
4797
- > );
4798
- > },
4799
- */
4800
- };
5163
+ if (isValidFilePath(knowledgeSourceContent)) {
5164
+ return makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose);
4801
5165
  }
5166
+ return makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose);
4802
5167
  }
4803
-
4804
5168
  /**
4805
- * Prepares the knowledge pieces
4806
- *
4807
- * @see https://github.com/webgptorg/promptbook/discussions/41
5169
+ * Creates a source handler for URL-based knowledge.
4808
5170
  *
4809
- * @public exported from `@promptbook/core`
5171
+ * @private internal utility of `makeKnowledgeSourceHandler`
4810
5172
  */
4811
- async function prepareKnowledgePieces(knowledgeSources, tools, options) {
4812
- const { maxParallelCount = DEFAULT_MAX_PARALLEL_COUNT, rootDirname, isVerbose = DEFAULT_IS_VERBOSE } = options;
4813
- const knowledgePreparedUnflatten = new Array(knowledgeSources.length);
4814
- await forEachAsync(knowledgeSources, { maxParallelCount }, async (knowledgeSource, index) => {
5173
+ async function makeUrlKnowledgeSourceHandler(url, name, tools, options, isVerbose) {
5174
+ var _a;
5175
+ const { fetch = promptbookFetch } = tools;
5176
+ if (isVerbose) {
5177
+ console.info(`📄 [1] "${name}" is available at "${url}"`);
5178
+ }
5179
+ const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
5180
+ const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
5181
+ if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
5182
+ return makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose);
5183
+ }
5184
+ return cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose);
5185
+ }
5186
+ /**
5187
+ * Creates a source handler that reads directly from a fetched response.
5188
+ *
5189
+ * @private internal utility of `makeKnowledgeSourceHandler`
5190
+ */
5191
+ function makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose) {
5192
+ if (isVerbose) {
5193
+ console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
5194
+ }
5195
+ return {
5196
+ source: name,
5197
+ filename: null,
5198
+ url,
5199
+ mimeType,
5200
+ /*
5201
+ TODO: [🥽]
5202
+ > async asBlob() {
5203
+ > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
5204
+ > const content = await response.blob();
5205
+ > return content;
5206
+ > },
5207
+ */
5208
+ async asJson() {
5209
+ // TODO: [👨🏻‍🤝‍👨🏻]
5210
+ const content = await response.json();
5211
+ return content;
5212
+ },
5213
+ async asText() {
5214
+ // TODO: [👨🏻‍🤝‍👨🏻]
5215
+ const content = await response.text();
5216
+ return content;
5217
+ },
5218
+ };
5219
+ }
5220
+ /**
5221
+ * Downloads a PDF knowledge source into cache when possible and falls back to in-memory content otherwise.
5222
+ *
5223
+ * @private internal utility of `makeKnowledgeSourceHandler`
5224
+ */
5225
+ async function cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose) {
5226
+ const rootDirname = path.join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5227
+ const filepath = createDownloadedKnowledgeSourceFilepath(url, mimeType);
5228
+ const fullFilepath = path.join(rootDirname, filepath);
5229
+ await createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose);
5230
+ const fileContent = Buffer.from(await response.arrayBuffer());
5231
+ if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5232
+ 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.`);
5233
+ }
5234
+ const isCached = await writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose);
5235
+ if (!isCached) {
5236
+ return makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent);
5237
+ }
5238
+ // TODO: [💵] Check the file security
5239
+ // TODO: [🧹][🧠] Delete the file after the scraping is done
5240
+ if (isVerbose) {
5241
+ console.info(`📄 [5] "${name}" cached at "${fullFilepath}"`);
5242
+ }
5243
+ return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
5244
+ ...options,
5245
+ rootDirname,
5246
+ });
5247
+ }
5248
+ /**
5249
+ * Builds a stable cache filepath for a downloaded knowledge source.
5250
+ *
5251
+ * @private internal utility of `makeKnowledgeSourceHandler`
5252
+ */
5253
+ function createDownloadedKnowledgeSourceFilepath(url, mimeType) {
5254
+ const basename = url.split('/').pop() || titleToName(url);
5255
+ const hash = sha256__default["default"](hexEncoder__default["default"].parse(url)).toString( /* hex */);
5256
+ // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5257
+ return path.join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
5258
+ }
5259
+ /**
5260
+ * Tries to create the cache directory needed for a downloaded knowledge source.
5261
+ *
5262
+ * @private internal utility of `makeKnowledgeSourceHandler`
5263
+ */
5264
+ async function createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose) {
5265
+ try {
5266
+ await tools.fs.mkdir(path.dirname(fullFilepath), { recursive: true });
5267
+ }
5268
+ catch (error) {
5269
+ if (isVerbose) {
5270
+ console.info(`📄 [3] "${name}" error creating cache directory`);
5271
+ }
5272
+ // Note: If we can't create cache directory, we'll handle it when trying to write the file
5273
+ // This handles read-only filesystems, permission issues, and missing parent directories
5274
+ if (!isIgnorableCacheFilesystemError(error)) {
5275
+ throw error;
5276
+ }
5277
+ }
5278
+ }
5279
+ /**
5280
+ * Tries to write downloaded content into cache and reports whether the cache was usable.
5281
+ *
5282
+ * @private internal utility of `makeKnowledgeSourceHandler`
5283
+ */
5284
+ async function writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose) {
5285
+ // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
5286
+ try {
5287
+ await tools.fs.writeFile(fullFilepath, fileContent);
5288
+ return true;
5289
+ }
5290
+ catch (error) {
5291
+ if (isVerbose) {
5292
+ console.info(`📄 [4] "${name}" error writing cache file`);
5293
+ }
5294
+ // Note: If we can't write to cache, we'll process the file directly from memory
5295
+ // This handles read-only filesystems like Vercel
5296
+ if (isIgnorableCacheFilesystemError(error)) {
5297
+ return false;
5298
+ }
5299
+ throw error;
5300
+ }
5301
+ }
5302
+ /**
5303
+ * Detects filesystem errors that should not fail optional caching.
5304
+ *
5305
+ * @private internal utility of `makeKnowledgeSourceHandler`
5306
+ */
5307
+ function isIgnorableCacheFilesystemError(error) {
5308
+ return (error instanceof Error &&
5309
+ (error.message.includes('EROFS') ||
5310
+ error.message.includes('read-only') ||
5311
+ error.message.includes('EACCES') ||
5312
+ error.message.includes('EPERM') ||
5313
+ error.message.includes('ENOENT')));
5314
+ }
5315
+ /**
5316
+ * Creates a source handler backed by already downloaded file content kept in memory.
5317
+ *
5318
+ * @private internal utility of `makeKnowledgeSourceHandler`
5319
+ */
5320
+ function makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent) {
5321
+ return {
5322
+ source: name,
5323
+ filename: null,
5324
+ url,
5325
+ mimeType,
5326
+ async asJson() {
5327
+ return JSON.parse(fileContent.toString('utf-8'));
5328
+ },
5329
+ async asText() {
5330
+ return fileContent.toString('utf-8');
5331
+ },
5332
+ };
5333
+ }
5334
+ /**
5335
+ * Creates a source handler for file-based knowledge.
5336
+ *
5337
+ * @private internal utility of `makeKnowledgeSourceHandler`
5338
+ */
5339
+ async function makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose) {
5340
+ if (tools.fs === undefined) {
5341
+ throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
5342
+ // <- TODO: [🧠] What is the best error type here`
5343
+ }
5344
+ if (rootDirname === null) {
5345
+ throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
5346
+ // <- TODO: [🧠] What is the best error type here`
5347
+ }
5348
+ const filename = path.isAbsolute(knowledgeSourceContent)
5349
+ ? knowledgeSourceContent
5350
+ : path.join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5351
+ if (isVerbose) {
5352
+ console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5353
+ }
5354
+ const fileExtension = getFileExtension(filename);
5355
+ const mimeType = extensionToMimeType(fileExtension || '');
5356
+ if (!(await isFileExisting(filename, tools.fs))) {
5357
+ throw new NotFoundError(spacetrim.spaceTrim((block) => `
5358
+ Can not make source handler for file which does not exist:
5359
+
5360
+ File:
5361
+ ${block(knowledgeSourceContent)}
5362
+
5363
+ Full file path:
5364
+ ${block(filename)}
5365
+ `));
5366
+ }
5367
+ // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
5368
+ return {
5369
+ source: name,
5370
+ filename,
5371
+ url: null,
5372
+ mimeType,
5373
+ /*
5374
+ TODO: [🥽]
5375
+ > async asBlob() {
5376
+ > const content = await tools.fs!.readFile(filename);
5377
+ > return new Blob(
5378
+ > [
5379
+ > content,
5380
+ > // <- TODO: [🥽] This is NOT tested, test it
5381
+ > ],
5382
+ > { type: mimeType },
5383
+ > );
5384
+ > },
5385
+ */
5386
+ async asJson() {
5387
+ return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
5388
+ },
5389
+ async asText() {
5390
+ return await tools.fs.readFile(filename, 'utf-8');
5391
+ },
5392
+ };
5393
+ }
5394
+ /**
5395
+ * Creates a source handler for inline text knowledge.
5396
+ *
5397
+ * @private internal utility of `makeKnowledgeSourceHandler`
5398
+ */
5399
+ function makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose) {
5400
+ if (isVerbose) {
5401
+ console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
5402
+ console.info('---');
5403
+ console.info(knowledgeSourceContent);
5404
+ console.info('---');
5405
+ }
5406
+ return {
5407
+ source: name,
5408
+ filename: null,
5409
+ url: null,
5410
+ mimeType: 'text/markdown',
5411
+ asText() {
5412
+ return knowledgeSourceContent;
5413
+ },
5414
+ asJson() {
5415
+ throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
5416
+ },
5417
+ /*
5418
+ TODO: [🥽]
5419
+ > asBlob() {
5420
+ > throw new UnexpectedError(
5421
+ > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
5422
+ > );
5423
+ > },
5424
+ */
5425
+ };
5426
+ }
5427
+
5428
+ /**
5429
+ * Prepares the knowledge pieces
5430
+ *
5431
+ * @see https://github.com/webgptorg/promptbook/discussions/41
5432
+ *
5433
+ * @public exported from `@promptbook/core`
5434
+ */
5435
+ async function prepareKnowledgePieces(knowledgeSources, tools, options) {
5436
+ const { maxParallelCount = DEFAULT_MAX_PARALLEL_COUNT, rootDirname, isVerbose = DEFAULT_IS_VERBOSE } = options;
5437
+ const knowledgePreparedUnflatten = new Array(knowledgeSources.length);
5438
+ await forEachAsync(knowledgeSources, { maxParallelCount }, async (knowledgeSource, index) => {
4815
5439
  try {
4816
5440
  let partialPieces = null;
4817
5441
  const sourceHandler = await makeKnowledgeSourceHandler(knowledgeSource, tools, { rootDirname, isVerbose });
@@ -6083,112 +6707,10 @@
6083
6707
  }
6084
6708
  }
6085
6709
 
6086
- /**
6087
- * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
6088
- * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
6089
- * Throws errors if execution fails after all attempts.
6090
- *
6091
- * @param options - The options for execution, including task, parameters, pipeline, and configuration.
6092
- * @returns The result string of the executed task.
6093
- *
6094
- * @private internal utility of `createPipelineExecutor`
6095
- */
6096
- async function executeAttempts(options) {
6097
- const $ongoingTaskResult = createOngoingTaskResult();
6098
- const llmTools = getSingleLlmExecutionTools(options.tools.llm);
6099
- attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
6100
- const attempt = createAttemptDescriptor({
6101
- attemptIndex,
6102
- jokerParameterNames: options.jokerParameterNames,
6103
- pipelineIdentification: options.pipelineIdentification,
6104
- });
6105
- resetAttemptExecutionState($ongoingTaskResult);
6106
- try {
6107
- await executeSingleAttempt({
6108
- attempt,
6109
- options,
6110
- llmTools,
6111
- $ongoingTaskResult,
6112
- });
6113
- break attempts;
6114
- }
6115
- catch (error) {
6116
- if (!(error instanceof ExpectError)) {
6117
- throw error;
6118
- }
6119
- recordFailedAttempt({
6120
- error,
6121
- attemptIndex,
6122
- onProgress: options.onProgress,
6123
- $ongoingTaskResult,
6124
- });
6125
- }
6126
- finally {
6127
- reportPromptExecution({
6128
- attempt,
6129
- task: options.task,
6130
- $executionReport: options.$executionReport,
6131
- logLlmCall: options.logLlmCall,
6132
- $ongoingTaskResult,
6133
- });
6134
- }
6135
- throwIfFinalAttemptFailed({
6136
- attemptIndex,
6137
- maxAttempts: options.maxAttempts,
6138
- maxExecutionAttempts: options.maxExecutionAttempts,
6139
- pipelineIdentification: options.pipelineIdentification,
6140
- $ongoingTaskResult,
6141
- });
6142
- }
6143
- return getSuccessfulResultString({
6144
- pipelineIdentification: options.pipelineIdentification,
6145
- $ongoingTaskResult,
6146
- });
6147
- }
6148
- /**
6149
- * Creates mutable attempt state for one task execution lifecycle.
6150
- */
6151
- function createOngoingTaskResult() {
6152
- return {
6153
- $result: null,
6154
- $resultString: null,
6155
- $expectError: null,
6156
- $scriptPipelineExecutionErrors: [],
6157
- $failedResults: [],
6158
- };
6159
- }
6160
- /**
6161
- * Resolves the bookkeeping for one loop iteration, including joker lookup.
6162
- */
6163
- function createAttemptDescriptor(options) {
6164
- const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
6165
- const isJokerAttempt = attemptIndex < 0;
6166
- const jokerParameterName = isJokerAttempt
6167
- ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
6168
- : undefined;
6169
- if (isJokerAttempt && !jokerParameterName) {
6170
- throw new UnexpectedError(spacetrim.spaceTrim((block) => `
6171
- Joker not found in attempt ${attemptIndex}
6172
-
6173
- ${block(pipelineIdentification)}
6174
- `));
6175
- }
6176
- return {
6177
- attemptIndex,
6178
- isJokerAttempt,
6179
- jokerParameterName,
6180
- };
6181
- }
6182
- /**
6183
- * Clears the per-attempt result slots while preserving cumulative failure history.
6184
- */
6185
- function resetAttemptExecutionState($ongoingTaskResult) {
6186
- $ongoingTaskResult.$result = null;
6187
- $ongoingTaskResult.$resultString = null;
6188
- $ongoingTaskResult.$expectError = null;
6189
- }
6190
6710
  /**
6191
6711
  * Executes one loop iteration, from joker resolution or task execution through validation.
6712
+ *
6713
+ * @private function of `executeAttempts`
6192
6714
  */
6193
6715
  async function executeSingleAttempt(options) {
6194
6716
  const { attempt, options: executeAttemptsOptions, llmTools, $ongoingTaskResult } = options;
@@ -6503,11 +7025,15 @@
6503
7025
  // Update the result string in case format processing modified it (e.g., JSON extraction)
6504
7026
  $ongoingTaskResult.$resultString = validationResult.processedResultString;
6505
7027
  }
7028
+
6506
7029
  /**
6507
- * Stores one failed attempt and reports the expectation error upstream.
7030
+ * Stores one failed attempt, reports the expectation error, and throws the aggregated retry error after the final
7031
+ * regular attempt.
7032
+ *
7033
+ * @private function of `executeAttempts`
6508
7034
  */
6509
- function recordFailedAttempt(options) {
6510
- const { error, attemptIndex, onProgress, $ongoingTaskResult } = options;
7035
+ function handleAttemptFailure(options) {
7036
+ const { error, attemptIndex, maxAttempts, maxExecutionAttempts, onProgress, pipelineIdentification, $ongoingTaskResult, } = options;
6511
7037
  $ongoingTaskResult.$expectError = error;
6512
7038
  $ongoingTaskResult.$failedResults.push({
6513
7039
  attemptIndex,
@@ -6517,39 +7043,7 @@
6517
7043
  onProgress({
6518
7044
  errors: [error],
6519
7045
  });
6520
- }
6521
- /**
6522
- * Appends the prompt execution report for prompt-task attempts.
6523
- */
6524
- function reportPromptExecution(options) {
6525
- const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
6526
- if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
6527
- return;
6528
- }
6529
- // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
6530
- // In that case we don’t want to make a report about it because it’s not a llm execution error
6531
- const executionPromptReport = {
6532
- prompt: {
6533
- ...$ongoingTaskResult.$prompt,
6534
- // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
6535
- },
6536
- result: $ongoingTaskResult.$result || undefined,
6537
- error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
6538
- };
6539
- $executionReport.promptExecutions.push(executionPromptReport);
6540
- if (logLlmCall) {
6541
- logLlmCall({
6542
- modelName: 'model' /* <- TODO: How to get model name from the report */,
6543
- report: executionPromptReport,
6544
- });
6545
- }
6546
- }
6547
- /**
6548
- * Throws the aggregated retry error after the last regular attempt fails expectations.
6549
- */
6550
- function throwIfFinalAttemptFailed(options) {
6551
- const { attemptIndex, maxAttempts, maxExecutionAttempts, pipelineIdentification, $ongoingTaskResult } = options;
6552
- if ($ongoingTaskResult.$expectError === null || attemptIndex !== maxAttempts - 1) {
7046
+ if (attemptIndex !== maxAttempts - 1) {
6553
7047
  return;
6554
7048
  }
6555
7049
  throw new PipelineExecutionError(spacetrim.spaceTrim((block) => {
@@ -6580,7 +7074,9 @@
6580
7074
  ${block(quoteMultilineText(((_b = failure.error) === null || _b === void 0 ? void 0 : _b.message) || ''))}
6581
7075
 
6582
7076
  Result:
6583
- ${block(failure.result === null ? 'null' : quoteMultilineText(spacetrim.spaceTrim(failure.result)))}
7077
+ ${block(failure.result === null
7078
+ ? 'null'
7079
+ : quoteMultilineText(spacetrim.spaceTrim(failure.result)))}
6584
7080
  `;
6585
7081
  }))
6586
7082
  .join('\n\n---\n\n');
@@ -6594,6 +7090,136 @@
6594
7090
  .map((line) => `> ${line}`)
6595
7091
  .join('\n');
6596
7092
  }
7093
+
7094
+ /**
7095
+ * Appends the prompt execution report for prompt-task attempts.
7096
+ *
7097
+ * @private function of `executeAttempts`
7098
+ */
7099
+ function reportPromptExecution(options) {
7100
+ const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
7101
+ if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
7102
+ return;
7103
+ }
7104
+ // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
7105
+ // In that case we don’t want to make a report about it because it’s not a llm execution error
7106
+ const executionPromptReport = {
7107
+ prompt: {
7108
+ ...$ongoingTaskResult.$prompt,
7109
+ // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
7110
+ },
7111
+ result: $ongoingTaskResult.$result || undefined,
7112
+ error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
7113
+ };
7114
+ $executionReport.promptExecutions.push(executionPromptReport);
7115
+ if (logLlmCall) {
7116
+ logLlmCall({
7117
+ modelName: 'model' /* <- TODO: How to get model name from the report */,
7118
+ report: executionPromptReport,
7119
+ });
7120
+ }
7121
+ }
7122
+
7123
+ /**
7124
+ * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
7125
+ * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
7126
+ * Throws errors if execution fails after all attempts.
7127
+ *
7128
+ * @param options - The options for execution, including task, parameters, pipeline, and configuration.
7129
+ * @returns The result string of the executed task.
7130
+ *
7131
+ * @private internal utility of `createPipelineExecutor`
7132
+ */
7133
+ async function executeAttempts(options) {
7134
+ const $ongoingTaskResult = createOngoingTaskResult();
7135
+ const llmTools = getSingleLlmExecutionTools(options.tools.llm);
7136
+ attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
7137
+ const attempt = createAttemptDescriptor({
7138
+ attemptIndex,
7139
+ jokerParameterNames: options.jokerParameterNames,
7140
+ pipelineIdentification: options.pipelineIdentification,
7141
+ });
7142
+ resetAttemptExecutionState($ongoingTaskResult);
7143
+ try {
7144
+ await executeSingleAttempt({
7145
+ attempt,
7146
+ options,
7147
+ llmTools,
7148
+ $ongoingTaskResult,
7149
+ });
7150
+ break attempts;
7151
+ }
7152
+ catch (error) {
7153
+ if (!(error instanceof ExpectError)) {
7154
+ throw error;
7155
+ }
7156
+ handleAttemptFailure({
7157
+ error,
7158
+ attemptIndex,
7159
+ maxAttempts: options.maxAttempts,
7160
+ maxExecutionAttempts: options.maxExecutionAttempts,
7161
+ onProgress: options.onProgress,
7162
+ pipelineIdentification: options.pipelineIdentification,
7163
+ $ongoingTaskResult,
7164
+ });
7165
+ }
7166
+ finally {
7167
+ reportPromptExecution({
7168
+ attempt,
7169
+ task: options.task,
7170
+ $executionReport: options.$executionReport,
7171
+ logLlmCall: options.logLlmCall,
7172
+ $ongoingTaskResult,
7173
+ });
7174
+ }
7175
+ }
7176
+ return getSuccessfulResultString({
7177
+ pipelineIdentification: options.pipelineIdentification,
7178
+ $ongoingTaskResult,
7179
+ });
7180
+ }
7181
+ /**
7182
+ * Creates mutable attempt state for one task execution lifecycle.
7183
+ */
7184
+ function createOngoingTaskResult() {
7185
+ return {
7186
+ $result: null,
7187
+ $resultString: null,
7188
+ $expectError: null,
7189
+ $scriptPipelineExecutionErrors: [],
7190
+ $failedResults: [],
7191
+ };
7192
+ }
7193
+ /**
7194
+ * Resolves the bookkeeping for one loop iteration, including joker lookup.
7195
+ */
7196
+ function createAttemptDescriptor(options) {
7197
+ const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
7198
+ const isJokerAttempt = attemptIndex < 0;
7199
+ const jokerParameterName = isJokerAttempt
7200
+ ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
7201
+ : undefined;
7202
+ if (isJokerAttempt && !jokerParameterName) {
7203
+ throw new UnexpectedError(spacetrim.spaceTrim((block) => `
7204
+ Joker not found in attempt ${attemptIndex}
7205
+
7206
+ ${block(pipelineIdentification)}
7207
+ `));
7208
+ }
7209
+ return {
7210
+ attemptIndex,
7211
+ isJokerAttempt,
7212
+ jokerParameterName,
7213
+ };
7214
+ }
7215
+ /**
7216
+ * Clears the per-attempt result slots while preserving cumulative failure history.
7217
+ */
7218
+ function resetAttemptExecutionState($ongoingTaskResult) {
7219
+ $ongoingTaskResult.$result = null;
7220
+ $ongoingTaskResult.$resultString = null;
7221
+ $ongoingTaskResult.$expectError = null;
7222
+ }
6597
7223
  /**
6598
7224
  * Returns the successful result string or raises an unexpected internal-state error.
6599
7225
  */