@promptbook/website-crawler 0.112.0-72 → 0.112.0-79

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 (214) hide show
  1. package/README.md +9 -9
  2. package/esm/index.es.js +1782 -1053
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  5. package/esm/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  6. package/esm/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  7. package/esm/src/book-components/Chat/save/_common/chatExportRendering.d.ts +75 -0
  8. package/esm/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  9. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +13 -1
  10. package/esm/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  11. package/esm/src/book-components/Chat/save/index.d.ts +5 -5
  12. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  13. package/esm/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  14. package/esm/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +4 -3
  15. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +3 -3
  16. package/esm/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  17. package/esm/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  18. package/esm/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  19. package/esm/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  20. package/esm/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +2 -0
  21. package/esm/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  22. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  23. package/esm/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  24. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  25. package/esm/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  26. package/esm/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  27. package/esm/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  28. package/esm/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  29. package/esm/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  30. package/esm/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  31. package/esm/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  32. package/esm/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  33. package/esm/src/cli/cli-commands/agents-server.d.ts +8 -0
  34. package/esm/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  35. package/esm/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  36. package/esm/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  37. package/esm/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  38. package/esm/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  39. package/esm/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  40. package/esm/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  41. package/esm/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  42. package/esm/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  43. package/esm/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  44. package/esm/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  45. package/esm/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  46. package/esm/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  47. package/esm/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  48. package/esm/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  49. package/esm/src/execution/resolveTaskTldr.d.ts +32 -0
  50. package/esm/src/execution/resolveTaskTldr.test.d.ts +1 -0
  51. package/esm/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  52. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  53. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  54. package/esm/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  55. package/esm/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  56. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  57. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  58. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  59. package/esm/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  60. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  61. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  62. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  63. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  64. package/esm/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  65. package/esm/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  66. package/esm/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  67. package/esm/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  68. package/esm/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  69. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  70. package/esm/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  71. package/esm/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  72. package/esm/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  73. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  74. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  75. package/esm/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  76. package/esm/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  77. package/esm/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  78. package/esm/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  79. package/esm/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  80. package/esm/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  81. package/esm/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  82. package/esm/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  83. package/esm/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  84. package/esm/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  85. package/esm/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  86. package/esm/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  87. package/esm/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  88. package/esm/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  89. package/esm/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  90. package/esm/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  91. package/esm/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  92. package/esm/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  93. package/esm/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  94. package/esm/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  95. package/esm/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  96. package/esm/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  97. package/esm/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  98. package/esm/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  99. package/esm/src/utils/color/Color.d.ts +4 -44
  100. package/esm/src/utils/color/ColorValue.d.ts +55 -0
  101. package/esm/src/utils/color/isHexColorString.d.ts +10 -0
  102. package/esm/src/utils/color/parseColorString.d.ts +11 -0
  103. package/esm/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  104. package/esm/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  105. package/esm/src/version.d.ts +1 -1
  106. package/package.json +2 -2
  107. package/umd/index.umd.js +1782 -1053
  108. package/umd/index.umd.js.map +1 -1
  109. package/umd/src/avatars/types/AvatarVisualDefinition.d.ts +1 -1
  110. package/umd/src/avatars/visuals/octopus3d2AvatarVisual.d.ts +7 -0
  111. package/umd/src/avatars/visuals/octopus3dAvatarVisualShared.d.ts +37 -0
  112. package/umd/src/book-components/Chat/save/_common/chatExportRendering.d.ts +75 -0
  113. package/umd/src/book-components/Chat/save/_common/getPromptbookExportBranding.d.ts +18 -0
  114. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.d.ts +13 -1
  115. package/umd/src/book-components/Chat/save/html/htmlSaveFormatDefinition.test.d.ts +1 -0
  116. package/umd/src/book-components/Chat/save/index.d.ts +5 -5
  117. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.d.ts +5 -3
  118. package/umd/src/book-components/Chat/save/markdown/mdSaveFormatDefinition.test.d.ts +1 -0
  119. package/umd/src/book-components/Chat/save/pdf/buildChatPdf.d.ts +4 -3
  120. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.d.ts +3 -3
  121. package/umd/src/book-components/Chat/save/pdf/pdfSaveFormatDefinition.test.d.ts +1 -0
  122. package/umd/src/book-components/Chat/save/react/reactSaveFormatDefinition.test.d.ts +1 -0
  123. package/umd/src/book-components/Chat/utils/renderMarkdown.d.ts +26 -0
  124. package/umd/src/cli/cli-commands/agent/agentProjectPaths.d.ts +8 -8
  125. package/umd/src/cli/cli-commands/agent/agentRunCliOptions.d.ts +2 -0
  126. package/umd/src/cli/cli-commands/agent/initializeAgentRunnerCommand.d.ts +1 -0
  127. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.d.ts +56 -0
  128. package/umd/src/cli/cli-commands/agents-server/buildAgentsServer.test.d.ts +1 -0
  129. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerEnvFile.d.ts +7 -0
  130. package/umd/src/cli/cli-commands/agents-server/ensureAgentsServerGitignoreFile.d.ts +7 -0
  131. package/umd/src/cli/cli-commands/agents-server/init.d.ts +9 -0
  132. package/umd/src/cli/cli-commands/agents-server/init.test.d.ts +1 -0
  133. package/umd/src/cli/cli-commands/agents-server/initializeAgentsServerProjectConfiguration.d.ts +17 -0
  134. package/umd/src/cli/cli-commands/agents-server/printAgentsServerInitializationSummary.d.ts +7 -0
  135. package/umd/src/cli/cli-commands/agents-server/run.d.ts +14 -0
  136. package/umd/src/cli/cli-commands/agents-server/run.test.d.ts +1 -0
  137. package/umd/src/cli/cli-commands/agents-server/startAgentsServer.d.ts +23 -0
  138. package/umd/src/cli/cli-commands/agents-server.d.ts +8 -0
  139. package/umd/src/cli/cli-commands/common/handleActionErrors.d.ts +9 -4
  140. package/umd/src/cli/cli-commands/common/projectInitialization.d.ts +65 -0
  141. package/umd/src/cli/cli-commands/common/promptRunnerCliOptions.d.ts +44 -0
  142. package/umd/src/cli/common/$deprecateCliCommand.d.ts +8 -0
  143. package/umd/src/cli/common/$deprecateCliCommand.test.d.ts +1 -0
  144. package/umd/src/conversion/pipelineJsonToString/appendMarkdownBlock.d.ts +7 -0
  145. package/umd/src/conversion/pipelineJsonToString/createPipelineCommands.d.ts +7 -0
  146. package/umd/src/conversion/pipelineJsonToString/createPipelineIntroduction.d.ts +8 -0
  147. package/umd/src/conversion/pipelineJsonToString/createTaskSerialization.d.ts +23 -0
  148. package/umd/src/conversion/pipelineJsonToString/stringifyCommands.d.ts +7 -0
  149. package/umd/src/conversion/pipelineJsonToString/stringifyTask.d.ts +8 -0
  150. package/umd/src/conversion/pipelineJsonToString.test.d.ts +1 -0
  151. package/umd/src/execution/createPipelineExecutor/executeSingleAttempt.d.ts +31 -0
  152. package/umd/src/execution/createPipelineExecutor/handleAttemptFailure.d.ts +40 -0
  153. package/umd/src/execution/createPipelineExecutor/reportPromptExecution.d.ts +34 -0
  154. package/umd/src/execution/resolveTaskTldr.d.ts +32 -0
  155. package/umd/src/execution/resolveTaskTldr.test.d.ts +1 -0
  156. package/umd/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +22 -63
  157. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsAgentKitRunner.d.ts +51 -0
  158. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsOpenAiAssistantRunner.d.ts +43 -0
  159. package/umd/src/llm-providers/agent/AgentLlmExecutionToolsPromptPreparer.d.ts +41 -0
  160. package/umd/src/llm-providers/agent/emitAgentLlmExecutionToolsAssistantPreparationProgress.d.ts +26 -0
  161. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionTools.d.ts +16 -93
  162. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsInputBuilder.d.ts +41 -0
  163. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsOutputTypeMapper.d.ts +56 -0
  164. package/umd/src/llm-providers/openai/OpenAiAgentKitExecutionToolsToolBuilder.d.ts +99 -0
  165. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +24 -120
  166. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsProgressReporter.d.ts +62 -0
  167. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsPromptBuilder.d.ts +29 -0
  168. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsStreamRunner.d.ts +63 -0
  169. package/umd/src/llm-providers/openai/OpenAiAssistantExecutionToolsToolRunner.d.ts +89 -0
  170. package/umd/src/llm-providers/openai/OpenAiCompatibleExecutionTools.d.ts +9 -28
  171. package/umd/src/llm-providers/openai/OpenAiCompatibleModelCatalog.d.ts +31 -0
  172. package/umd/src/llm-providers/openai/OpenAiCompatibleNonChatPromptCaller.d.ts +57 -0
  173. package/umd/src/llm-providers/openai/OpenAiCompatibleRequestManager.d.ts +29 -0
  174. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchHandler.d.ts +51 -0
  175. package/umd/src/llm-providers/openai/OpenAiVectorStoreFileBatchPoller.d.ts +75 -0
  176. package/umd/src/llm-providers/openai/OpenAiVectorStoreHandler.d.ts +1 -98
  177. package/umd/src/llm-providers/openai/OpenAiVectorStoreKnowledgeSourcePreparer.d.ts +44 -0
  178. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatProgressReporter.d.ts +86 -0
  179. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatPromptBuilder.d.ts +57 -0
  180. package/umd/src/llm-providers/openai/utils/OpenAiCompatibleChatToolCaller.d.ts +57 -0
  181. package/umd/src/remote-server/startRemoteServer/RemoteServerRuntime.d.ts +14 -0
  182. package/umd/src/remote-server/startRemoteServer/SocketResponse.d.ts +9 -0
  183. package/umd/src/remote-server/startRemoteServer/StartRemoteServerConfiguration.d.ts +18 -0
  184. package/umd/src/remote-server/startRemoteServer/createRemoteServerExpressApp.d.ts +7 -0
  185. package/umd/src/remote-server/startRemoteServer/createRemoteServerHandle.d.ts +11 -0
  186. package/umd/src/remote-server/startRemoteServer/createSocketServer.d.ts +9 -0
  187. package/umd/src/remote-server/startRemoteServer/getExecutionToolsFromIdentification.d.ts +12 -0
  188. package/umd/src/remote-server/startRemoteServer/registerBookRoutes.d.ts +7 -0
  189. package/umd/src/remote-server/startRemoteServer/registerExecutionRoutes.d.ts +7 -0
  190. package/umd/src/remote-server/startRemoteServer/registerListModelsSocketHandler.d.ts +8 -0
  191. package/umd/src/remote-server/startRemoteServer/registerLoginRoute.d.ts +7 -0
  192. package/umd/src/remote-server/startRemoteServer/registerNotFoundRoute.d.ts +7 -0
  193. package/umd/src/remote-server/startRemoteServer/registerOpenAiCompatibleChatCompletionsRoute.d.ts +7 -0
  194. package/umd/src/remote-server/startRemoteServer/registerOpenApiRoutes.d.ts +7 -0
  195. package/umd/src/remote-server/startRemoteServer/registerPreparePipelineSocketHandler.d.ts +8 -0
  196. package/umd/src/remote-server/startRemoteServer/registerPromptSocketHandler.d.ts +8 -0
  197. package/umd/src/remote-server/startRemoteServer/registerRemoteServerHttpRoutes.d.ts +7 -0
  198. package/umd/src/remote-server/startRemoteServer/registerRemoteServerSocketHandlers.d.ts +8 -0
  199. package/umd/src/remote-server/startRemoteServer/registerServerIndexRoute.d.ts +7 -0
  200. package/umd/src/remote-server/startRemoteServer/resolveStartRemoteServerConfiguration.d.ts +8 -0
  201. package/umd/src/remote-server/startRemoteServer/respondToSocketRequest.d.ts +8 -0
  202. package/umd/src/remote-server/startRemoteServer/startListening.d.ts +9 -0
  203. package/umd/src/scrapers/_common/utils/makeKnowledgeSourceHandler.d.ts +14 -1
  204. package/umd/src/utils/color/Color.d.ts +4 -44
  205. package/umd/src/utils/color/ColorValue.d.ts +55 -0
  206. package/umd/src/utils/color/isHexColorString.d.ts +10 -0
  207. package/umd/src/utils/color/parseColorString.d.ts +11 -0
  208. package/umd/src/utils/serialization/serializeToPromptbookJavascript.d.ts +2 -0
  209. package/umd/src/utils/serialization/serializeToPromptbookJavascript.test.d.ts +1 -0
  210. package/umd/src/version.d.ts +1 -1
  211. package/esm/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  212. package/esm/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
  213. package/umd/src/cli/cli-commands/coder/appendBlock.d.ts +0 -6
  214. package/umd/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +0 -6
package/esm/index.es.js CHANGED
@@ -27,7 +27,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
27
27
  * @generated
28
28
  * @see https://github.com/webgptorg/promptbook
29
29
  */
30
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
30
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
31
31
  /**
32
32
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
33
33
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -321,6 +321,111 @@ function checkChannelValue(channelName, value) {
321
321
  }
322
322
  }
323
323
 
324
+ /**
325
+ * Shared immutable channel storage and serialization helpers for `Color`.
326
+ *
327
+ * @private base class of Color
328
+ */
329
+ class ColorValue {
330
+ constructor(red, green, blue, alpha = 255) {
331
+ this.red = red;
332
+ this.green = green;
333
+ this.blue = blue;
334
+ this.alpha = alpha;
335
+ checkChannelValue('Red', red);
336
+ checkChannelValue('Green', green);
337
+ checkChannelValue('Blue', blue);
338
+ checkChannelValue('Alpha', alpha);
339
+ }
340
+ /**
341
+ * Shortcut for `red` property
342
+ * Number from 0 to 255
343
+ * @alias red
344
+ */
345
+ get r() {
346
+ return this.red;
347
+ }
348
+ /**
349
+ * Shortcut for `green` property
350
+ * Number from 0 to 255
351
+ * @alias green
352
+ */
353
+ get g() {
354
+ return this.green;
355
+ }
356
+ /**
357
+ * Shortcut for `blue` property
358
+ * Number from 0 to 255
359
+ * @alias blue
360
+ */
361
+ get b() {
362
+ return this.blue;
363
+ }
364
+ /**
365
+ * Shortcut for `alpha` property
366
+ * Number from 0 (transparent) to 255 (opaque)
367
+ * @alias alpha
368
+ */
369
+ get a() {
370
+ return this.alpha;
371
+ }
372
+ /**
373
+ * Shortcut for `alpha` property
374
+ * Number from 0 (transparent) to 255 (opaque)
375
+ * @alias alpha
376
+ */
377
+ get opacity() {
378
+ return this.alpha;
379
+ }
380
+ /**
381
+ * Shortcut for 1-`alpha` property
382
+ */
383
+ get transparency() {
384
+ return 255 - this.alpha;
385
+ }
386
+ clone() {
387
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
388
+ }
389
+ toString() {
390
+ return this.toHex();
391
+ }
392
+ toHex() {
393
+ if (this.alpha === 255) {
394
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
395
+ .toString(16)
396
+ .padStart(2, '0')}`;
397
+ }
398
+ else {
399
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
400
+ .toString(16)
401
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
402
+ }
403
+ }
404
+ toRgb() {
405
+ if (this.alpha === 255) {
406
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
407
+ }
408
+ else {
409
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
410
+ }
411
+ }
412
+ toHsl() {
413
+ throw new Error(`Getting HSL is not implemented`);
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Checks if the given value is a valid hex color string
419
+ *
420
+ * @param value - value to check
421
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
422
+ *
423
+ * @private function of Color
424
+ */
425
+ function isHexColorString(value) {
426
+ return (typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
427
+ }
428
+
324
429
  /**
325
430
  * Constant for short hex lengths.
326
431
  */
@@ -532,16 +637,53 @@ function parseAlphaValue(value) {
532
637
 
533
638
  /**
534
639
  * Pattern matching hsl regex.
640
+ *
641
+ * @private function of Color
535
642
  */
536
643
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
537
644
  /**
538
645
  * Pattern matching RGB regex.
646
+ *
647
+ * @private function of Color
539
648
  */
540
649
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
541
650
  /**
542
651
  * Pattern matching rgba regex.
652
+ *
653
+ * @private function of Color
543
654
  */
544
655
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
656
+ /**
657
+ * Parses a supported color string into RGBA channels.
658
+ *
659
+ * @param color as a string for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)`, `red`, `darkgrey`,...
660
+ * @returns RGBA channel values.
661
+ *
662
+ * @private function of Color
663
+ */
664
+ function parseColorString(color) {
665
+ const trimmed = color.trim();
666
+ const cssColor = CSS_COLORS[trimmed];
667
+ if (cssColor) {
668
+ return parseColorString(cssColor);
669
+ }
670
+ else if (isHexColorString(trimmed)) {
671
+ return parseHexColor(trimmed);
672
+ }
673
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
674
+ return parseHslColor(trimmed);
675
+ }
676
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
677
+ return parseRgbColor(trimmed);
678
+ }
679
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
680
+ return parseRgbaColor(trimmed);
681
+ }
682
+ else {
683
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
684
+ }
685
+ }
686
+
545
687
  /**
546
688
  * Color object represents an RGB color with alpha channel
547
689
  *
@@ -549,7 +691,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
549
691
  *
550
692
  * @public exported from `@promptbook/color`
551
693
  */
552
- class Color {
694
+ class Color extends ColorValue {
553
695
  /**
554
696
  * Creates a new Color instance from miscellaneous formats
555
697
  * - It can receive Color instance and just return the same instance
@@ -622,25 +764,7 @@ class Color {
622
764
  * @returns Color object
623
765
  */
624
766
  static fromString(color) {
625
- const trimmed = color.trim();
626
- if (CSS_COLORS[trimmed]) {
627
- return Color.fromString(CSS_COLORS[trimmed]);
628
- }
629
- else if (Color.isHexColorString(trimmed)) {
630
- return Color.fromHex(trimmed);
631
- }
632
- if (HSL_REGEX_PATTERN.test(trimmed)) {
633
- return Color.fromHsl(trimmed);
634
- }
635
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
636
- return Color.fromRgbString(trimmed);
637
- }
638
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
639
- return Color.fromRgbaString(trimmed);
640
- }
641
- else {
642
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
643
- }
767
+ return Color.fromColorChannels(parseColorString(color));
644
768
  }
645
769
  /**
646
770
  * Gets common color
@@ -670,8 +794,7 @@ class Color {
670
794
  * @returns Color object
671
795
  */
672
796
  static fromHex(hex) {
673
- const { red, green, blue, alpha } = parseHexColor(hex);
674
- return take(new Color(red, green, blue, alpha));
797
+ return Color.fromColorChannels(parseHexColor(hex));
675
798
  }
676
799
  /**
677
800
  * Creates a new Color instance from color in hsl format
@@ -680,8 +803,7 @@ class Color {
680
803
  * @returns Color object
681
804
  */
682
805
  static fromHsl(hsl) {
683
- const { red, green, blue, alpha } = parseHslColor(hsl);
684
- return take(new Color(red, green, blue, alpha));
806
+ return Color.fromColorChannels(parseHslColor(hsl));
685
807
  }
686
808
  /**
687
809
  * Creates a new Color instance from color in rgb format
@@ -690,8 +812,7 @@ class Color {
690
812
  * @returns Color object
691
813
  */
692
814
  static fromRgbString(rgb) {
693
- const { red, green, blue, alpha } = parseRgbColor(rgb);
694
- return take(new Color(red, green, blue, alpha));
815
+ return Color.fromColorChannels(parseRgbColor(rgb));
695
816
  }
696
817
  /**
697
818
  * Creates a new Color instance from color in rbga format
@@ -700,8 +821,7 @@ class Color {
700
821
  * @returns Color object
701
822
  */
702
823
  static fromRgbaString(rgba) {
703
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
704
- return take(new Color(red, green, blue, alpha));
824
+ return Color.fromColorChannels(parseRgbaColor(rgba));
705
825
  }
706
826
  /**
707
827
  * Creates a new Color for color channels values
@@ -713,7 +833,7 @@ class Color {
713
833
  * @returns Color object
714
834
  */
715
835
  static fromValues(red, green, blue, alpha = 255) {
716
- return take(new Color(red, green, blue, alpha));
836
+ return Color.fromColorChannels({ red, green, blue, alpha });
717
837
  }
718
838
  /**
719
839
  * Checks if the given value is a valid Color object.
@@ -746,8 +866,7 @@ class Color {
746
866
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
747
867
  */
748
868
  static isHexColorString(value) {
749
- return (typeof value === 'string' &&
750
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
869
+ return isHexColorString(value);
751
870
  }
752
871
  /**
753
872
  * Creates new Color object
@@ -760,89 +879,13 @@ class Color {
760
879
  * @param alpha number from 0 (transparent) to 255 (opaque)
761
880
  */
762
881
  constructor(red, green, blue, alpha = 255) {
763
- this.red = red;
764
- this.green = green;
765
- this.blue = blue;
766
- this.alpha = alpha;
767
- checkChannelValue('Red', red);
768
- checkChannelValue('Green', green);
769
- checkChannelValue('Blue', blue);
770
- checkChannelValue('Alpha', alpha);
771
- }
772
- /**
773
- * Shortcut for `red` property
774
- * Number from 0 to 255
775
- * @alias red
776
- */
777
- get r() {
778
- return this.red;
779
- }
780
- /**
781
- * Shortcut for `green` property
782
- * Number from 0 to 255
783
- * @alias green
784
- */
785
- get g() {
786
- return this.green;
787
- }
788
- /**
789
- * Shortcut for `blue` property
790
- * Number from 0 to 255
791
- * @alias blue
792
- */
793
- get b() {
794
- return this.blue;
795
- }
796
- /**
797
- * Shortcut for `alpha` property
798
- * Number from 0 (transparent) to 255 (opaque)
799
- * @alias alpha
800
- */
801
- get a() {
802
- return this.alpha;
803
- }
804
- /**
805
- * Shortcut for `alpha` property
806
- * Number from 0 (transparent) to 255 (opaque)
807
- * @alias alpha
808
- */
809
- get opacity() {
810
- return this.alpha;
811
- }
812
- /**
813
- * Shortcut for 1-`alpha` property
814
- */
815
- get transparency() {
816
- return 255 - this.alpha;
817
- }
818
- clone() {
819
- return take(new Color(this.red, this.green, this.blue, this.alpha));
820
- }
821
- toString() {
822
- return this.toHex();
823
- }
824
- toHex() {
825
- if (this.alpha === 255) {
826
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
827
- .toString(16)
828
- .padStart(2, '0')}`;
829
- }
830
- else {
831
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
832
- .toString(16)
833
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
834
- }
882
+ super(red, green, blue, alpha);
835
883
  }
836
- toRgb() {
837
- if (this.alpha === 255) {
838
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
839
- }
840
- else {
841
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
842
- }
884
+ createColor(red, green, blue, alpha) {
885
+ return new Color(red, green, blue, alpha);
843
886
  }
844
- toHsl() {
845
- throw new Error(`Getting HSL is not implemented`);
887
+ static fromColorChannels({ red, green, blue, alpha }) {
888
+ return take(new Color(red, green, blue, alpha));
846
889
  }
847
890
  }
848
891
 
@@ -1776,6 +1819,60 @@ function validatePipelineString(pipelineString) {
1776
1819
  }
1777
1820
  // TODO: [🧠][🈴] Where is the best location for this file
1778
1821
 
1822
+ /**
1823
+ * Appends one markdown block to an existing markdown document.
1824
+ *
1825
+ * @private internal utility of `pipelineJsonToString`
1826
+ */
1827
+ function appendMarkdownBlock(pipelineString, markdownBlock) {
1828
+ return spaceTrim$1((block) => `
1829
+ ${block(pipelineString)}
1830
+
1831
+ ${block(markdownBlock)}
1832
+ `);
1833
+ }
1834
+
1835
+ /**
1836
+ * Collects pipeline-level commands in the existing serialization order.
1837
+ *
1838
+ * @private internal utility of `pipelineJsonToString`
1839
+ */
1840
+ function createPipelineCommands(pipelineJson) {
1841
+ const { pipelineUrl, bookVersion, parameters } = pipelineJson;
1842
+ const commands = [];
1843
+ if (pipelineUrl) {
1844
+ commands.push(`PIPELINE URL ${pipelineUrl}`);
1845
+ }
1846
+ if (bookVersion !== `undefined`) {
1847
+ commands.push(`BOOK VERSION ${bookVersion}`);
1848
+ }
1849
+ commands.push(...createParameterCommands(parameters, 'INPUT PARAMETER', ({ isInput }) => isInput));
1850
+ commands.push(...createParameterCommands(parameters, 'OUTPUT PARAMETER', ({ isOutput }) => isOutput));
1851
+ return commands;
1852
+ }
1853
+ /**
1854
+ * Builds one group of parameter commands while preserving the original parameter order.
1855
+ *
1856
+ * @private internal utility of `createPipelineCommands`
1857
+ */
1858
+ function createParameterCommands(parameters, commandPrefix, isIncluded) {
1859
+ return parameters
1860
+ .filter((parameter) => isIncluded(parameter))
1861
+ .map((parameter) => `${commandPrefix} ${parameterJsonToString(parameter)}`);
1862
+ }
1863
+ /**
1864
+ * Converts one parameter JSON declaration to the serialized inline form.
1865
+ *
1866
+ * @private internal utility of `createPipelineCommands`
1867
+ */
1868
+ function parameterJsonToString(parameterJson) {
1869
+ const { name, description } = parameterJson;
1870
+ if (!description) {
1871
+ return `{${name}}`;
1872
+ }
1873
+ return `{${name}} ${description}`;
1874
+ }
1875
+
1779
1876
  /**
1780
1877
  * Prettify the html code
1781
1878
  *
@@ -1790,152 +1887,222 @@ function prettifyMarkdown(content) {
1790
1887
  }
1791
1888
 
1792
1889
  /**
1793
- * Makes first letter of a string uppercase
1794
- *
1795
- * Note: [🔂] This function is idempotent.
1890
+ * Creates the initial markdown heading and description of a pipeline.
1796
1891
  *
1797
- * @public exported from `@promptbook/utils`
1892
+ * @private internal utility of `pipelineJsonToString`
1798
1893
  */
1799
- function capitalize(word) {
1800
- return word.substring(0, 1).toUpperCase() + word.substring(1);
1894
+ function createPipelineIntroduction(pipelineJson) {
1895
+ const { title, description } = pipelineJson;
1896
+ const pipelineIntroduction = spaceTrim$1((block) => `
1897
+ # ${title}
1898
+
1899
+ ${block(description || '')}
1900
+ `);
1901
+ // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1902
+ return prettifyMarkdown(pipelineIntroduction);
1801
1903
  }
1802
1904
 
1803
1905
  /**
1804
- * Converts promptbook in JSON format to string format
1906
+ * Renders commands as markdown bullet items.
1805
1907
  *
1806
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1807
- * @param pipelineJson Promptbook in JSON format (.bookc)
1808
- * @returns Promptbook in string format (.book.md)
1908
+ * @private internal utility of `pipelineJsonToString`
1909
+ */
1910
+ function stringifyCommands(commands) {
1911
+ return commands.map((command) => `- ${command}`).join('\n');
1912
+ }
1913
+
1914
+ /**
1915
+ * Makes first letter of a string uppercase
1809
1916
  *
1810
- * @public exported from `@promptbook/core`
1917
+ * Note: [🔂] This function is idempotent.
1918
+ *
1919
+ * @public exported from `@promptbook/utils`
1811
1920
  */
1812
- function pipelineJsonToString(pipelineJson) {
1813
- const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
1814
- let pipelineString = spaceTrim$1((block) => `
1815
- # ${title}
1921
+ function capitalize(word) {
1922
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
1923
+ }
1816
1924
 
1817
- ${block(description || '')}
1818
- `);
1925
+ /**
1926
+ * Collects all task-specific serialization details.
1927
+ *
1928
+ * @private internal utility of `pipelineJsonToString`
1929
+ */
1930
+ function createTaskSerialization(task) {
1931
+ const taskTypeSerialization = createTaskTypeSerialization(task);
1932
+ return {
1933
+ commands: [
1934
+ ...taskTypeSerialization.commands,
1935
+ ...createJokerCommands(task),
1936
+ ...createPostprocessingCommands(task),
1937
+ ...createExpectationCommands(task),
1938
+ ...createFormatCommands(task),
1939
+ ],
1940
+ contentLanguage: taskTypeSerialization.contentLanguage,
1941
+ };
1942
+ }
1943
+ /**
1944
+ * Collects commands and content language driven by the task type.
1945
+ *
1946
+ * @private internal utility of `createTaskSerialization`
1947
+ */
1948
+ function createTaskTypeSerialization(task) {
1949
+ if (task.taskType === 'PROMPT_TASK') {
1950
+ return {
1951
+ commands: createPromptTaskCommands(task),
1952
+ contentLanguage: 'text',
1953
+ };
1954
+ }
1955
+ if (task.taskType === 'SIMPLE_TASK') {
1956
+ return {
1957
+ commands: ['SIMPLE TEMPLATE'],
1958
+ contentLanguage: 'text',
1959
+ };
1960
+ }
1961
+ if (task.taskType === 'SCRIPT_TASK') {
1962
+ return {
1963
+ commands: ['SCRIPT'],
1964
+ contentLanguage: task.contentLanguage || '',
1965
+ };
1966
+ }
1967
+ if (task.taskType === 'DIALOG_TASK') {
1968
+ return {
1969
+ commands: ['DIALOG'],
1970
+ contentLanguage: 'text',
1971
+ };
1972
+ }
1973
+ return {
1974
+ commands: [],
1975
+ contentLanguage: 'text',
1976
+ };
1977
+ }
1978
+ /**
1979
+ * Collects prompt-task-specific commands.
1980
+ *
1981
+ * @private internal utility of `createTaskSerialization`
1982
+ */
1983
+ function createPromptTaskCommands(task) {
1984
+ const { modelName, modelVariant } = task.modelRequirements || {};
1819
1985
  const commands = [];
1820
- if (pipelineUrl) {
1821
- commands.push(`PIPELINE URL ${pipelineUrl}`);
1986
+ // Note: Do nothing, it is default
1987
+ // commands.push(`PROMPT`);
1988
+ if (modelVariant) {
1989
+ commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1822
1990
  }
1823
- if (bookVersion !== `undefined`) {
1824
- commands.push(`BOOK VERSION ${bookVersion}`);
1991
+ if (modelName) {
1992
+ commands.push(`MODEL NAME \`${modelName}\``);
1825
1993
  }
1826
- // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1827
- pipelineString = prettifyMarkdown(pipelineString);
1828
- for (const parameter of parameters.filter(({ isInput }) => isInput)) {
1829
- commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
1994
+ return commands;
1995
+ }
1996
+ /**
1997
+ * Collects joker commands.
1998
+ *
1999
+ * @private internal utility of `createTaskSerialization`
2000
+ */
2001
+ function createJokerCommands(task) {
2002
+ var _a;
2003
+ return ((_a = task.jokerParameterNames) === null || _a === void 0 ? void 0 : _a.map((joker) => `JOKER {${joker}}`)) || [];
2004
+ }
2005
+ /**
2006
+ * Collects postprocessing commands.
2007
+ *
2008
+ * @private internal utility of `createTaskSerialization`
2009
+ */
2010
+ function createPostprocessingCommands(task) {
2011
+ var _a;
2012
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
2013
+ }
2014
+ /**
2015
+ * Collects expectation commands.
2016
+ *
2017
+ * @private internal utility of `createTaskSerialization`
2018
+ */
2019
+ function createExpectationCommands(task) {
2020
+ if (!task.expectations) {
2021
+ return [];
1830
2022
  }
1831
- for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
1832
- commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
2023
+ return Object.entries(task.expectations).flatMap(([unit, expectation]) => createExpectationCommandsForUnit(unit, expectation.min, expectation.max));
2024
+ }
2025
+ /**
2026
+ * Collects expectation commands for a single unit.
2027
+ *
2028
+ * @private internal utility of `createTaskSerialization`
2029
+ */
2030
+ function createExpectationCommandsForUnit(unit, min, max) {
2031
+ if (min === max) {
2032
+ return [`EXPECT EXACTLY ${min} ${formatExpectationUnit(unit, min)}`];
1833
2033
  }
1834
- pipelineString = spaceTrim$1((block) => `
1835
- ${block(pipelineString)}
1836
-
1837
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
1838
- `);
1839
- for (const task of tasks) {
1840
- const {
1841
- /* Note: Not using:> name, */
1842
- title, description,
1843
- /* Note: dependentParameterNames, */
1844
- jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
1845
- const commands = [];
1846
- let contentLanguage = 'text';
1847
- if (taskType === 'PROMPT_TASK') {
1848
- const { modelRequirements } = task;
1849
- const { modelName, modelVariant } = modelRequirements || {};
1850
- // Note: Do nothing, it is default
1851
- // commands.push(`PROMPT`);
1852
- if (modelVariant) {
1853
- commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1854
- }
1855
- if (modelName) {
1856
- commands.push(`MODEL NAME \`${modelName}\``);
1857
- }
1858
- }
1859
- else if (taskType === 'SIMPLE_TASK') {
1860
- commands.push(`SIMPLE TEMPLATE`);
1861
- // Note: Nothing special here
1862
- }
1863
- else if (taskType === 'SCRIPT_TASK') {
1864
- commands.push(`SCRIPT`);
1865
- if (task.contentLanguage) {
1866
- contentLanguage = task.contentLanguage;
1867
- }
1868
- else {
1869
- contentLanguage = '';
1870
- }
1871
- }
1872
- else if (taskType === 'DIALOG_TASK') {
1873
- commands.push(`DIALOG`);
1874
- // Note: Nothing special here
1875
- } // <- }else if([🅱]
1876
- if (jokers) {
1877
- for (const joker of jokers) {
1878
- commands.push(`JOKER {${joker}}`);
1879
- }
1880
- } /* not else */
1881
- if (postprocessing) {
1882
- for (const postprocessingFunctionName of postprocessing) {
1883
- commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
1884
- }
1885
- } /* not else */
1886
- if (expectations) {
1887
- for (const [unit, { min, max }] of Object.entries(expectations)) {
1888
- if (min === max) {
1889
- commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1890
- }
1891
- else {
1892
- if (min !== undefined) {
1893
- commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1894
- } /* not else */
1895
- if (max !== undefined) {
1896
- commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
1897
- }
1898
- }
1899
- }
1900
- } /* not else */
1901
- if (format) {
1902
- if (format === 'JSON') {
1903
- // TODO: @deprecated remove
1904
- commands.push(`FORMAT JSON`);
1905
- }
1906
- } /* not else */
1907
- pipelineString = spaceTrim$1((block) => `
1908
- ${block(pipelineString)}
2034
+ const commands = [];
2035
+ if (min !== undefined) {
2036
+ commands.push(`EXPECT MIN ${min} ${formatExpectationUnit(unit, min)}`);
2037
+ }
2038
+ if (max !== undefined) {
2039
+ commands.push(`EXPECT MAX ${max} ${formatExpectationUnit(unit, max)}`);
2040
+ }
2041
+ return commands;
2042
+ }
2043
+ /**
2044
+ * Formats the expectation unit exactly as the legacy serializer does.
2045
+ *
2046
+ * @private internal utility of `createTaskSerialization`
2047
+ */
2048
+ function formatExpectationUnit(unit, amount) {
2049
+ return capitalize(unit + (amount > 1 ? 's' : ''));
2050
+ }
2051
+ /**
2052
+ * Collects format commands.
2053
+ *
2054
+ * @private internal utility of `createTaskSerialization`
2055
+ */
2056
+ function createFormatCommands(task) {
2057
+ if (task.format === 'JSON') {
2058
+ // TODO: @deprecated remove
2059
+ return ['FORMAT JSON'];
2060
+ }
2061
+ return [];
2062
+ }
1909
2063
 
1910
- ## ${title}
2064
+ /**
2065
+ * Stringifies one task section of the pipeline.
2066
+ *
2067
+ * @private internal utility of `pipelineJsonToString`
2068
+ */
2069
+ function stringifyTask(task) {
2070
+ const { title, description, content, resultingParameterName } = task;
2071
+ const { commands, contentLanguage } = createTaskSerialization(task);
2072
+ return spaceTrim$1((block) => `
2073
+ ## ${title}
1911
2074
 
1912
- ${block(description || '')}
2075
+ ${block(description || '')}
1913
2076
 
1914
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
2077
+ ${block(stringifyCommands(commands))}
1915
2078
 
1916
- \`\`\`${contentLanguage}
1917
- ${block(spaceTrim$1(content))}
1918
- \`\`\`
2079
+ \`\`\`${contentLanguage}
2080
+ ${block(spaceTrim$1(content))}
2081
+ \`\`\`
1919
2082
 
1920
- \`-> {${resultingParameterName}}\`
1921
- `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1922
- // <- TODO: [main] !!3 Escape
1923
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1924
- }
1925
- return validatePipelineString(pipelineString);
2083
+ \`-> {${resultingParameterName}}\`
2084
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
2085
+ // <- TODO: [main] !!3 Escape
2086
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1926
2087
  }
2088
+
1927
2089
  /**
1928
- * Handles task parameter Json to string.
2090
+ * Converts promptbook in JSON format to string format
1929
2091
  *
1930
- * @private internal utility of `pipelineJsonToString`
2092
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
2093
+ * @param pipelineJson Promptbook in JSON format (.bookc)
2094
+ * @returns Promptbook in string format (.book.md)
2095
+ *
2096
+ * @public exported from `@promptbook/core`
1931
2097
  */
1932
- function taskParameterJsonToString(taskParameterJson) {
1933
- const { name, description } = taskParameterJson;
1934
- let parameterString = `{${name}}`;
1935
- if (description) {
1936
- parameterString = `${parameterString} ${description}`;
2098
+ function pipelineJsonToString(pipelineJson) {
2099
+ let pipelineString = createPipelineIntroduction(pipelineJson);
2100
+ const pipelineCommands = createPipelineCommands(pipelineJson);
2101
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
2102
+ for (const task of pipelineJson.tasks) {
2103
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
1937
2104
  }
1938
- return parameterString;
2105
+ return validatePipelineString(pipelineString);
1939
2106
  }
1940
2107
  // TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
1941
2108
  // TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
@@ -1981,120 +2148,183 @@ function orderJson(options) {
1981
2148
  * @public exported from `@promptbook/utils`
1982
2149
  */
1983
2150
  function checkSerializableAsJson(options) {
1984
- const { value, name, message } = options;
2151
+ checkSerializableValue(options);
2152
+ }
2153
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2154
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2155
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2156
+ /**
2157
+ * Checks one value and dispatches to the appropriate specialized validator.
2158
+ *
2159
+ * @private function of `checkSerializableAsJson`
2160
+ */
2161
+ function checkSerializableValue(options) {
2162
+ const { value } = options;
2163
+ if (isSerializablePrimitive(value)) {
2164
+ return;
2165
+ }
1985
2166
  if (value === undefined) {
1986
- throw new UnexpectedError(`${name} is undefined`);
2167
+ throw new UnexpectedError(`${options.name} is undefined`);
1987
2168
  }
1988
- else if (value === null) {
1989
- return;
2169
+ if (typeof value === 'symbol') {
2170
+ throw new UnexpectedError(`${options.name} is symbol`);
1990
2171
  }
1991
- else if (typeof value === 'boolean') {
1992
- return;
2172
+ if (typeof value === 'function') {
2173
+ throw new UnexpectedError(`${options.name} is function`);
1993
2174
  }
1994
- else if (typeof value === 'number' && !isNaN(value)) {
2175
+ if (Array.isArray(value)) {
2176
+ checkSerializableArray(options, value);
1995
2177
  return;
1996
2178
  }
1997
- else if (typeof value === 'string') {
2179
+ if (value !== null && typeof value === 'object') {
2180
+ checkSerializableObject(options, value);
1998
2181
  return;
1999
2182
  }
2000
- else if (typeof value === 'symbol') {
2001
- throw new UnexpectedError(`${name} is symbol`);
2002
- }
2003
- else if (typeof value === 'function') {
2004
- throw new UnexpectedError(`${name} is function`);
2005
- }
2006
- else if (typeof value === 'object' && Array.isArray(value)) {
2007
- for (let i = 0; i < value.length; i++) {
2008
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
2009
- }
2183
+ throwUnknownTypeError(options);
2184
+ }
2185
+ /**
2186
+ * Checks the primitive values that are directly JSON serializable.
2187
+ *
2188
+ * @private function of `checkSerializableAsJson`
2189
+ */
2190
+ function isSerializablePrimitive(value) {
2191
+ return (value === null ||
2192
+ typeof value === 'boolean' ||
2193
+ (typeof value === 'number' && !isNaN(value)) ||
2194
+ typeof value === 'string');
2195
+ }
2196
+ /**
2197
+ * Recursively checks JSON array items.
2198
+ *
2199
+ * @private function of `checkSerializableAsJson`
2200
+ */
2201
+ function checkSerializableArray(context, arrayValue) {
2202
+ for (let index = 0; index < arrayValue.length; index++) {
2203
+ checkSerializableAsJson({
2204
+ ...context,
2205
+ name: `${context.name}[${index}]`,
2206
+ value: arrayValue[index],
2207
+ });
2010
2208
  }
2011
- else if (typeof value === 'object') {
2012
- if (value instanceof Date) {
2013
- throw new UnexpectedError(spaceTrim$1((block) => `
2014
- \`${name}\` is Date
2209
+ }
2210
+ /**
2211
+ * Checks object-like values and dispatches special unsupported built-ins.
2212
+ *
2213
+ * @private function of `checkSerializableAsJson`
2214
+ */
2215
+ function checkSerializableObject(context, objectValue) {
2216
+ checkUnsupportedObjectType(context, objectValue);
2217
+ checkSerializableObjectEntries(context, objectValue);
2218
+ assertJsonStringificationSucceeds(context, objectValue);
2219
+ }
2220
+ /**
2221
+ * Rejects built-in objects that must be converted before JSON serialization.
2222
+ *
2223
+ * @private function of `checkSerializableAsJson`
2224
+ */
2225
+ function checkUnsupportedObjectType(context, objectValue) {
2226
+ if (objectValue instanceof Date) {
2227
+ throw new UnexpectedError(spaceTrim$1((block) => `
2228
+ \`${context.name}\` is Date
2015
2229
 
2016
- Use \`string_date_iso8601\` instead
2230
+ Use \`string_date_iso8601\` instead
2017
2231
 
2018
- Additional message for \`${name}\`:
2019
- ${block(message || '(nothing)')}
2020
- `));
2021
- }
2022
- else if (value instanceof Map) {
2023
- throw new UnexpectedError(`${name} is Map`);
2024
- }
2025
- else if (value instanceof Set) {
2026
- throw new UnexpectedError(`${name} is Set`);
2027
- }
2028
- else if (value instanceof RegExp) {
2029
- throw new UnexpectedError(`${name} is RegExp`);
2030
- }
2031
- else if (value instanceof Error) {
2032
- throw new UnexpectedError(spaceTrim$1((block) => `
2033
- \`${name}\` is unserialized Error
2232
+ Additional message for \`${context.name}\`:
2233
+ ${block(context.message || '(nothing)')}
2234
+ `));
2235
+ }
2236
+ if (objectValue instanceof Map) {
2237
+ throw new UnexpectedError(`${context.name} is Map`);
2238
+ }
2239
+ if (objectValue instanceof Set) {
2240
+ throw new UnexpectedError(`${context.name} is Set`);
2241
+ }
2242
+ if (objectValue instanceof RegExp) {
2243
+ throw new UnexpectedError(`${context.name} is RegExp`);
2244
+ }
2245
+ if (objectValue instanceof Error) {
2246
+ throw new UnexpectedError(spaceTrim$1((block) => `
2247
+ \`${context.name}\` is unserialized Error
2034
2248
 
2035
- Use function \`serializeError\`
2249
+ Use function \`serializeError\`
2036
2250
 
2037
- Additional message for \`${name}\`:
2038
- ${block(message || '(nothing)')}
2251
+ Additional message for \`${context.name}\`:
2252
+ ${block(context.message || '(nothing)')}
2039
2253
 
2040
- `));
2254
+ `));
2255
+ }
2256
+ }
2257
+ /**
2258
+ * Recursively checks object properties while preserving omitted `undefined` keys.
2259
+ *
2260
+ * @private function of `checkSerializableAsJson`
2261
+ */
2262
+ function checkSerializableObjectEntries(context, objectValue) {
2263
+ for (const [subName, subValue] of Object.entries(objectValue)) {
2264
+ if (subValue === undefined) {
2265
+ // Note: undefined in object is serializable - it is just omitted
2266
+ continue;
2041
2267
  }
2042
- else {
2043
- for (const [subName, subValue] of Object.entries(value)) {
2044
- if (subValue === undefined) {
2045
- // Note: undefined in object is serializable - it is just omitted
2046
- continue;
2047
- }
2048
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
2049
- }
2050
- try {
2051
- JSON.stringify(value); // <- TODO: [0]
2052
- }
2053
- catch (error) {
2054
- assertsError(error);
2055
- throw new UnexpectedError(spaceTrim$1((block) => `
2056
- \`${name}\` is not serializable
2268
+ checkSerializableAsJson({
2269
+ ...context,
2270
+ name: `${context.name}.${subName}`,
2271
+ value: subValue,
2272
+ });
2273
+ }
2274
+ }
2275
+ /**
2276
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
2277
+ *
2278
+ * @private function of `checkSerializableAsJson`
2279
+ */
2280
+ function assertJsonStringificationSucceeds(context, objectValue) {
2281
+ try {
2282
+ JSON.stringify(objectValue); // <- TODO: [0]
2283
+ }
2284
+ catch (error) {
2285
+ assertsError(error);
2286
+ throw new UnexpectedError(spaceTrim$1((block) => `
2287
+ \`${context.name}\` is not serializable
2057
2288
 
2058
- ${block(error.stack || error.message)}
2289
+ ${block(error.stack || error.message)}
2059
2290
 
2060
- Additional message for \`${name}\`:
2061
- ${block(message || '(nothing)')}
2062
- `));
2291
+ Additional message for \`${context.name}\`:
2292
+ ${block(context.message || '(nothing)')}
2293
+ `));
2294
+ }
2295
+ /*
2296
+ TODO: [0] Is there some more elegant way to check circular references?
2297
+ const seen = new Set();
2298
+ const stack = [{ value }];
2299
+ while (stack.length > 0) {
2300
+ const { value } = stack.pop()!;
2301
+ if (typeof value === 'object' && value !== null) {
2302
+ if (seen.has(value)) {
2303
+ throw new UnexpectedError(`${name} has circular reference`);
2063
2304
  }
2064
- /*
2065
- TODO: [0] Is there some more elegant way to check circular references?
2066
- const seen = new Set();
2067
- const stack = [{ value }];
2068
- while (stack.length > 0) {
2069
- const { value } = stack.pop()!;
2070
- if (typeof value === 'object' && value !== null) {
2071
- if (seen.has(value)) {
2072
- throw new UnexpectedError(`${name} has circular reference`);
2073
- }
2074
- seen.add(value);
2075
- if (Array.isArray(value)) {
2076
- stack.push(...value.map((value) => ({ value })));
2077
- } else {
2078
- stack.push(...Object.values(value).map((value) => ({ value })));
2079
- }
2080
- }
2305
+ seen.add(value);
2306
+ if (Array.isArray(value)) {
2307
+ stack.push(...value.map((value) => ({ value })));
2308
+ } else {
2309
+ stack.push(...Object.values(value).map((value) => ({ value })));
2081
2310
  }
2082
- */
2083
- return;
2084
2311
  }
2085
2312
  }
2086
- else {
2087
- throw new UnexpectedError(spaceTrim$1((block) => `
2088
- \`${name}\` is unknown type
2313
+ */
2314
+ }
2315
+ /**
2316
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2317
+ *
2318
+ * @private function of `checkSerializableAsJson`
2319
+ */
2320
+ function throwUnknownTypeError(context) {
2321
+ throw new UnexpectedError(spaceTrim$1((block) => `
2322
+ \`${context.name}\` is unknown type
2089
2323
 
2090
- Additional message for \`${name}\`:
2091
- ${block(message || '(nothing)')}
2092
- `));
2093
- }
2324
+ Additional message for \`${context.name}\`:
2325
+ ${block(context.message || '(nothing)')}
2326
+ `));
2094
2327
  }
2095
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2096
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2097
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2098
2328
 
2099
2329
  /**
2100
2330
  * Creates a deep clone of the given object
@@ -2349,233 +2579,518 @@ function validatePipeline(pipeline) {
2349
2579
  */
2350
2580
  function validatePipeline_InnerFunction(pipeline) {
2351
2581
  // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
2352
- const pipelineIdentification = (() => {
2353
- // Note: This is a 😐 implementation of [🚞]
2354
- const _ = [];
2355
- if (pipeline.sourceFile !== undefined) {
2356
- _.push(`File: ${pipeline.sourceFile}`);
2357
- }
2358
- if (pipeline.pipelineUrl !== undefined) {
2359
- _.push(`Url: ${pipeline.pipelineUrl}`);
2360
- }
2361
- return _.join('\n');
2362
- })();
2363
- if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
2364
- // <- Note: [🚲]
2365
- throw new PipelineLogicError(spaceTrim$1((block) => `
2366
- Invalid promptbook URL "${pipeline.pipelineUrl}"
2367
-
2368
- ${block(pipelineIdentification)}
2369
- `));
2582
+ const context = createPipelineValidationContext(pipeline);
2583
+ validatePipelineMetadata(context);
2584
+ validatePipelineCollectionsStructure(context);
2585
+ validatePipelineParameters(context);
2586
+ validatePipelineTasks(context);
2587
+ validatePipelineDependencyResolution(context);
2588
+ // Note: Check that formfactor is corresponding to the pipeline interface
2589
+ // TODO: !!6 Implement this
2590
+ // pipeline.formfactorName
2591
+ }
2592
+ /**
2593
+ * Creates the shared validation context for one pipeline.
2594
+ *
2595
+ * @private internal utility of `validatePipeline`
2596
+ */
2597
+ function createPipelineValidationContext(pipeline) {
2598
+ return {
2599
+ pipeline,
2600
+ pipelineIdentification: getPipelineIdentification(pipeline),
2601
+ };
2602
+ }
2603
+ /**
2604
+ * Builds a short file/url identification block for validation errors.
2605
+ *
2606
+ * @private internal utility of `validatePipeline`
2607
+ */
2608
+ function getPipelineIdentification(pipeline) {
2609
+ // Note: This is a 😐 implementation of [🚞]
2610
+ const pipelineIdentificationParts = [];
2611
+ if (pipeline.sourceFile !== undefined) {
2612
+ pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
2370
2613
  }
2371
- if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
2372
- // <- Note: [🚲]
2373
- throw new PipelineLogicError(spaceTrim$1((block) => `
2374
- Invalid Promptbook Version "${pipeline.bookVersion}"
2614
+ if (pipeline.pipelineUrl !== undefined) {
2615
+ pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
2616
+ }
2617
+ return pipelineIdentificationParts.join('\n');
2618
+ }
2619
+ /**
2620
+ * Validates pipeline-level metadata fields.
2621
+ *
2622
+ * @private internal step of `validatePipeline`
2623
+ */
2624
+ function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
2625
+ validatePipelineUrl(pipeline, pipelineIdentification);
2626
+ validatePipelineBookVersion(pipeline, pipelineIdentification);
2627
+ }
2628
+ /**
2629
+ * Validates that the expected top-level collections have array structure.
2630
+ *
2631
+ * @private internal step of `validatePipeline`
2632
+ */
2633
+ function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification }) {
2634
+ validatePipelineParametersCollection(pipeline, pipelineIdentification);
2635
+ validatePipelineTasksCollection(pipeline, pipelineIdentification);
2636
+ }
2637
+ /**
2638
+ * Validates all pipeline parameter declarations.
2639
+ *
2640
+ * @private internal step of `validatePipeline`
2641
+ */
2642
+ function validatePipelineParameters({ pipeline, pipelineIdentification }) {
2643
+ for (const parameter of pipeline.parameters) {
2644
+ validatePipelineParameter(parameter, pipeline, pipelineIdentification);
2645
+ }
2646
+ }
2647
+ /**
2648
+ * Validates all pipeline tasks and their per-task invariants.
2649
+ *
2650
+ * @private internal step of `validatePipeline`
2651
+ */
2652
+ function validatePipelineTasks({ pipeline, pipelineIdentification }) {
2653
+ // Note: All input parameters are defined - so that they can be used as result of some task
2654
+ const definedParameters = createInitiallyDefinedParameters(pipeline);
2655
+ for (const task of pipeline.tasks) {
2656
+ validatePipelineTask(task, definedParameters, pipelineIdentification);
2657
+ }
2658
+ }
2659
+ /**
2660
+ * Validates that task dependencies can be resolved without cycles or missing definitions.
2661
+ *
2662
+ * @private internal step of `validatePipeline`
2663
+ */
2664
+ function validatePipelineDependencyResolution({ pipeline, pipelineIdentification }) {
2665
+ let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
2666
+ let loopLimit = LOOP_LIMIT;
2667
+ while (hasUnresolvedTasks(dependencyResolutionState)) {
2668
+ if (loopLimit-- < 0) {
2669
+ throw createDependencyResolutionLoopLimitError(pipelineIdentification);
2670
+ }
2671
+ dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
2672
+ }
2673
+ }
2674
+ /**
2675
+ * Validates one pipeline parameter declaration.
2676
+ *
2677
+ * @private internal step of `validatePipeline`
2678
+ */
2679
+ function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
2680
+ validateParameterDirection(parameter, pipelineIdentification);
2681
+ validateParameterUsage(parameter, pipeline, pipelineIdentification);
2682
+ validateParameterDefinition(parameter, pipeline, pipelineIdentification);
2683
+ }
2684
+ /**
2685
+ * Validates one pipeline task and its invariants.
2686
+ *
2687
+ * @private internal step of `validatePipeline`
2688
+ */
2689
+ function validatePipelineTask(task, definedParameters, pipelineIdentification) {
2690
+ validateTaskResultingParameter(task, definedParameters, pipelineIdentification);
2691
+ validateTaskJokers(task, pipelineIdentification);
2692
+ validateTaskExpectations(task, pipelineIdentification);
2693
+ }
2694
+ /**
2695
+ * Validates the pipeline URL, when present.
2696
+ *
2697
+ * @private internal utility of `validatePipeline`
2698
+ */
2699
+ function validatePipelineUrl(pipeline, pipelineIdentification) {
2700
+ if (pipeline.pipelineUrl === undefined || isValidPipelineUrl(pipeline.pipelineUrl)) {
2701
+ return;
2702
+ }
2703
+ // <- Note: [🚲]
2704
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2705
+ Invalid promptbook URL "${pipeline.pipelineUrl}"
2375
2706
 
2376
- ${block(pipelineIdentification)}
2377
- `));
2707
+ ${block(pipelineIdentification)}
2708
+ `));
2709
+ }
2710
+ /**
2711
+ * Validates the Promptbook version, when present.
2712
+ *
2713
+ * @private internal utility of `validatePipeline`
2714
+ */
2715
+ function validatePipelineBookVersion(pipeline, pipelineIdentification) {
2716
+ if (pipeline.bookVersion === undefined || isValidPromptbookVersion(pipeline.bookVersion)) {
2717
+ return;
2378
2718
  }
2719
+ // <- Note: [🚲]
2720
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2721
+ Invalid Promptbook Version "${pipeline.bookVersion}"
2722
+
2723
+ ${block(pipelineIdentification)}
2724
+ `));
2725
+ }
2726
+ /**
2727
+ * Validates that `pipeline.parameters` is an array.
2728
+ *
2729
+ * @private internal utility of `validatePipeline`
2730
+ */
2731
+ function validatePipelineParametersCollection(pipeline, pipelineIdentification) {
2379
2732
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2380
- if (!Array.isArray(pipeline.parameters)) {
2381
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2382
- throw new ParseError(spaceTrim$1((block) => `
2383
- Pipeline is valid JSON but with wrong structure
2733
+ if (Array.isArray(pipeline.parameters)) {
2734
+ return;
2735
+ }
2736
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2737
+ throw new ParseError(spaceTrim$1((block) => `
2738
+ Pipeline is valid JSON but with wrong structure
2384
2739
 
2385
- \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2740
+ \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2386
2741
 
2387
- ${block(pipelineIdentification)}
2388
- `));
2389
- }
2742
+ ${block(pipelineIdentification)}
2743
+ `));
2744
+ }
2745
+ /**
2746
+ * Validates that `pipeline.tasks` is an array.
2747
+ *
2748
+ * @private internal utility of `validatePipeline`
2749
+ */
2750
+ function validatePipelineTasksCollection(pipeline, pipelineIdentification) {
2390
2751
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2391
- if (!Array.isArray(pipeline.tasks)) {
2392
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2393
- throw new ParseError(spaceTrim$1((block) => `
2394
- Pipeline is valid JSON but with wrong structure
2752
+ if (Array.isArray(pipeline.tasks)) {
2753
+ return;
2754
+ }
2755
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2756
+ throw new ParseError(spaceTrim$1((block) => `
2757
+ Pipeline is valid JSON but with wrong structure
2395
2758
 
2396
- \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2759
+ \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2397
2760
 
2398
- ${block(pipelineIdentification)}
2399
- `));
2761
+ ${block(pipelineIdentification)}
2762
+ `));
2763
+ }
2764
+ /**
2765
+ * Validates that one parameter does not declare incompatible directions.
2766
+ *
2767
+ * @private internal utility of `validatePipeline`
2768
+ */
2769
+ function validateParameterDirection(parameter, pipelineIdentification) {
2770
+ if (!parameter.isInput || !parameter.isOutput) {
2771
+ return;
2400
2772
  }
2401
- /*
2402
- TODO: [🧠][🅾] Should be empty pipeline valid or not
2403
- // Note: Check that pipeline has some tasks
2404
- if (pipeline.tasks.length === 0) {
2405
- throw new PipelineLogicError(
2406
- spaceTrim(
2407
- (block) => `
2408
- Pipeline must have at least one task
2773
+ const parameterName = parameter.name;
2774
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2409
2775
 
2410
- ${block(pipelineIdentification)}
2411
- `,
2412
- ),
2413
- );
2776
+ Parameter \`{${parameterName}}\` can not be both input and output
2777
+
2778
+ ${block(pipelineIdentification)}
2779
+ `));
2780
+ }
2781
+ /**
2782
+ * Validates that one intermediate parameter is actually consumed by at least one task.
2783
+ *
2784
+ * @private internal utility of `validatePipeline`
2785
+ */
2786
+ function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
2787
+ if (parameter.isInput || parameter.isOutput || isParameterUsedByAnyTask(parameter, pipeline.tasks)) {
2788
+ return;
2414
2789
  }
2415
- */
2416
- // Note: Check each parameter individually
2417
- for (const parameter of pipeline.parameters) {
2418
- if (parameter.isInput && parameter.isOutput) {
2419
- throw new PipelineLogicError(spaceTrim$1((block) => `
2790
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2791
+ Parameter \`{${parameter.name}}\` is created but not used
2420
2792
 
2421
- Parameter \`{${parameter.name}}\` can not be both input and output
2793
+ You can declare {${parameter.name}} as output parameter by adding in the header:
2794
+ - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2422
2795
 
2423
- ${block(pipelineIdentification)}
2424
- `));
2425
- }
2426
- // Note: Testing that parameter is either intermediate or output BUT not created and unused
2427
- if (!parameter.isInput &&
2428
- !parameter.isOutput &&
2429
- !pipeline.tasks.some((task) => task.dependentParameterNames.includes(parameter.name))) {
2430
- throw new PipelineLogicError(spaceTrim$1((block) => `
2431
- Parameter \`{${parameter.name}}\` is created but not used
2432
-
2433
- You can declare {${parameter.name}} as output parameter by adding in the header:
2434
- - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2796
+ ${block(pipelineIdentification)}
2435
2797
 
2436
- ${block(pipelineIdentification)}
2798
+ `));
2799
+ }
2800
+ /**
2801
+ * Validates that one non-input parameter is produced by at least one task.
2802
+ *
2803
+ * @private internal utility of `validatePipeline`
2804
+ */
2805
+ function validateParameterDefinition(parameter, pipeline, pipelineIdentification) {
2806
+ if (parameter.isInput || isParameterDefinedByAnyTask(parameter, pipeline.tasks)) {
2807
+ return;
2808
+ }
2809
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2810
+ Parameter \`{${parameter.name}}\` is declared but not defined
2437
2811
 
2438
- `));
2439
- }
2440
- // Note: Testing that parameter is either input or result of some task
2441
- if (!parameter.isInput && !pipeline.tasks.some((task) => task.resultingParameterName === parameter.name)) {
2442
- throw new PipelineLogicError(spaceTrim$1((block) => `
2443
- Parameter \`{${parameter.name}}\` is declared but not defined
2812
+ You can do one of these:
2813
+ 1) Remove declaration of \`{${parameter.name}}\`
2814
+ 2) Add task that results in \`-> {${parameter.name}}\`
2444
2815
 
2445
- You can do one of these:
2446
- 1) Remove declaration of \`{${parameter.name}}\`
2447
- 2) Add task that results in \`-> {${parameter.name}}\`
2816
+ ${block(pipelineIdentification)}
2817
+ `));
2818
+ }
2819
+ /**
2820
+ * Checks whether one parameter is consumed by at least one task dependency list.
2821
+ *
2822
+ * @private internal utility of `validatePipeline`
2823
+ */
2824
+ function isParameterUsedByAnyTask(parameter, tasks) {
2825
+ return tasks.some((task) => task.dependentParameterNames.includes(parameter.name));
2826
+ }
2827
+ /**
2828
+ * Checks whether one parameter is produced by at least one task.
2829
+ *
2830
+ * @private internal utility of `validatePipeline`
2831
+ */
2832
+ function isParameterDefinedByAnyTask(parameter, tasks) {
2833
+ return tasks.some((task) => task.resultingParameterName === parameter.name);
2834
+ }
2835
+ /**
2836
+ * Collects the parameter names that are already defined before task validation starts.
2837
+ *
2838
+ * @private internal utility of `validatePipeline`
2839
+ */
2840
+ function createInitiallyDefinedParameters(pipeline) {
2841
+ return new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2842
+ }
2843
+ /**
2844
+ * Validates one task result parameter declaration and marks it as defined.
2845
+ *
2846
+ * @private internal utility of `validatePipeline`
2847
+ */
2848
+ function validateTaskResultingParameter(task, definedParameters, pipelineIdentification) {
2849
+ if (definedParameters.has(task.resultingParameterName)) {
2850
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2851
+ Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2448
2852
 
2449
- ${block(pipelineIdentification)}
2450
- `));
2451
- }
2853
+ ${block(pipelineIdentification)}
2854
+ `));
2452
2855
  }
2453
- // Note: All input parameters are defined - so that they can be used as result of some task
2454
- const definedParameters = new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2455
- // Note: Checking each task individually
2456
- for (const task of pipeline.tasks) {
2457
- if (definedParameters.has(task.resultingParameterName)) {
2458
- throw new PipelineLogicError(spaceTrim$1((block) => `
2459
- Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2856
+ if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2857
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2858
+ Parameter name {${task.resultingParameterName}} is reserved, please use different name
2460
2859
 
2461
- ${block(pipelineIdentification)}
2462
- `));
2463
- }
2464
- if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2465
- throw new PipelineLogicError(spaceTrim$1((block) => `
2466
- Parameter name {${task.resultingParameterName}} is reserved, please use different name
2860
+ ${block(pipelineIdentification)}
2861
+ `));
2862
+ }
2863
+ definedParameters.add(task.resultingParameterName);
2864
+ }
2865
+ /**
2866
+ * Validates joker parameters for one task.
2867
+ *
2868
+ * @private internal utility of `validatePipeline`
2869
+ */
2870
+ function validateTaskJokers(task, pipelineIdentification) {
2871
+ if (!hasTaskJokers(task)) {
2872
+ return;
2873
+ }
2874
+ validateTaskSupportsJokers(task, pipelineIdentification);
2875
+ validateTaskJokerDependencies(task, pipelineIdentification);
2876
+ }
2877
+ /**
2878
+ * Checks whether one task declares any joker parameters.
2879
+ *
2880
+ * @private internal utility of `validatePipeline`
2881
+ */
2882
+ function hasTaskJokers(task) {
2883
+ return !!task.jokerParameterNames && task.jokerParameterNames.length > 0;
2884
+ }
2885
+ /**
2886
+ * Validates that a task has the required supporting features when using jokers.
2887
+ *
2888
+ * @private internal utility of `validatePipeline`
2889
+ */
2890
+ function validateTaskSupportsJokers(task, pipelineIdentification) {
2891
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2892
+ return;
2893
+ }
2894
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2895
+ Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2467
2896
 
2468
- ${block(pipelineIdentification)}
2469
- `));
2897
+ ${block(pipelineIdentification)}
2898
+ `));
2899
+ }
2900
+ /**
2901
+ * Validates that every joker parameter is also listed among task dependencies.
2902
+ *
2903
+ * @private internal utility of `validatePipeline`
2904
+ */
2905
+ function validateTaskJokerDependencies(task, pipelineIdentification) {
2906
+ for (const joker of task.jokerParameterNames) {
2907
+ if (task.dependentParameterNames.includes(joker)) {
2908
+ continue;
2470
2909
  }
2471
- definedParameters.add(task.resultingParameterName);
2472
- if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
2473
- if (!task.format &&
2474
- !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2475
- throw new PipelineLogicError(spaceTrim$1((block) => `
2476
- Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2477
-
2478
- ${block(pipelineIdentification)}
2479
- `));
2480
- }
2481
- for (const joker of task.jokerParameterNames) {
2482
- if (!task.dependentParameterNames.includes(joker)) {
2483
- throw new PipelineLogicError(spaceTrim$1((block) => `
2484
- Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2910
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2911
+ Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2485
2912
 
2486
- ${block(pipelineIdentification)}
2487
- `));
2488
- }
2489
- }
2490
- }
2491
- if (task.expectations) {
2492
- for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2493
- if (min !== undefined && max !== undefined && min > max) {
2494
- throw new PipelineLogicError(spaceTrim$1((block) => `
2495
- Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2913
+ ${block(pipelineIdentification)}
2914
+ `));
2915
+ }
2916
+ }
2917
+ /**
2918
+ * Validates all expectation bounds configured on one task.
2919
+ *
2920
+ * @private internal utility of `validatePipeline`
2921
+ */
2922
+ function validateTaskExpectations(task, pipelineIdentification) {
2923
+ if (!task.expectations) {
2924
+ return;
2925
+ }
2926
+ for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2927
+ validateTaskExpectationRange(unit, min, max, pipelineIdentification);
2928
+ validateTaskExpectationMin(unit, min, pipelineIdentification);
2929
+ validateTaskExpectationMax(unit, max, pipelineIdentification);
2930
+ }
2931
+ }
2932
+ /**
2933
+ * Validates the minimum and maximum expectation ordering for one unit.
2934
+ *
2935
+ * @private internal utility of `validatePipeline`
2936
+ */
2937
+ function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
2938
+ if (min === undefined || max === undefined || min <= max) {
2939
+ return;
2940
+ }
2941
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2942
+ Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2496
2943
 
2497
- ${block(pipelineIdentification)}
2498
- `));
2499
- }
2500
- if (min !== undefined && min < 0) {
2501
- throw new PipelineLogicError(spaceTrim$1((block) => `
2502
- Min expectation of ${unit} must be zero or positive
2944
+ ${block(pipelineIdentification)}
2945
+ `));
2946
+ }
2947
+ /**
2948
+ * Validates the minimum expectation bound for one unit.
2949
+ *
2950
+ * @private internal utility of `validatePipeline`
2951
+ */
2952
+ function validateTaskExpectationMin(unit, min, pipelineIdentification) {
2953
+ if (min === undefined || min >= 0) {
2954
+ return;
2955
+ }
2956
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2957
+ Min expectation of ${unit} must be zero or positive
2503
2958
 
2504
- ${block(pipelineIdentification)}
2505
- `));
2506
- }
2507
- if (max !== undefined && max <= 0) {
2508
- throw new PipelineLogicError(spaceTrim$1((block) => `
2509
- Max expectation of ${unit} must be positive
2959
+ ${block(pipelineIdentification)}
2960
+ `));
2961
+ }
2962
+ /**
2963
+ * Validates the maximum expectation bound for one unit.
2964
+ *
2965
+ * @private internal utility of `validatePipeline`
2966
+ */
2967
+ function validateTaskExpectationMax(unit, max, pipelineIdentification) {
2968
+ if (max === undefined || max > 0) {
2969
+ return;
2970
+ }
2971
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2972
+ Max expectation of ${unit} must be positive
2510
2973
 
2511
- ${block(pipelineIdentification)}
2512
- `));
2513
- }
2514
- }
2515
- }
2974
+ ${block(pipelineIdentification)}
2975
+ `));
2976
+ }
2977
+ /**
2978
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
2979
+ *
2980
+ * @private internal utility of `validatePipeline`
2981
+ */
2982
+ function createInitialDependencyResolutionState(pipeline) {
2983
+ return {
2984
+ resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
2985
+ unresolvedTasks: [...pipeline.tasks],
2986
+ };
2987
+ }
2988
+ /**
2989
+ * Checks whether dependency resolution still has tasks left to process.
2990
+ *
2991
+ * @private internal utility of `validatePipeline`
2992
+ */
2993
+ function hasUnresolvedTasks({ unresolvedTasks }) {
2994
+ return unresolvedTasks.length > 0;
2995
+ }
2996
+ /**
2997
+ * Resolves the next batch of currently satisfiable tasks.
2998
+ *
2999
+ * @private internal utility of `validatePipeline`
3000
+ */
3001
+ function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
3002
+ const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
3003
+ if (currentlyResolvedTasks.length === 0) {
3004
+ throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
2516
3005
  }
2517
- // Note: Detect circular dependencies
2518
- let resovedParameters = pipeline.parameters
3006
+ return {
3007
+ resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
3008
+ unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
3009
+ };
3010
+ }
3011
+ /**
3012
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
3013
+ *
3014
+ * @private internal utility of `validatePipeline`
3015
+ */
3016
+ function createInitiallyResolvedParameterNames(pipeline) {
3017
+ let resolvedParameterNames = pipeline.parameters
2519
3018
  .filter(({ isInput }) => isInput)
2520
3019
  .map(({ name }) => name);
2521
- // Note: All reserved parameters are resolved
2522
3020
  for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
2523
- resovedParameters = [...resovedParameters, reservedParameterName];
3021
+ resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
2524
3022
  }
2525
- let unresovedTasks = [...pipeline.tasks];
2526
- let loopLimit = LOOP_LIMIT;
2527
- while (unresovedTasks.length > 0) {
2528
- if (loopLimit-- < 0) {
2529
- // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2530
- throw new UnexpectedError(spaceTrim$1((block) => `
2531
- Loop limit reached during detection of circular dependencies in \`validatePipeline\`
3023
+ return resolvedParameterNames;
3024
+ }
3025
+ /**
3026
+ * Adds newly resolved task outputs to the resolved parameter list.
3027
+ *
3028
+ * @private internal utility of `validatePipeline`
3029
+ */
3030
+ function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
3031
+ return [
3032
+ ...resolvedParameterNames,
3033
+ ...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
3034
+ ];
3035
+ }
3036
+ /**
3037
+ * Selects tasks whose dependencies are already resolved.
3038
+ *
3039
+ * @private internal utility of `validatePipeline`
3040
+ */
3041
+ function getCurrentlyResolvedTasks(unresolvedTasks, resolvedParameterNames) {
3042
+ return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
3043
+ }
3044
+ /**
3045
+ * Creates the unexpected loop-limit error for dependency resolution.
3046
+ *
3047
+ * @private internal utility of `validatePipeline`
3048
+ */
3049
+ function createDependencyResolutionLoopLimitError(pipelineIdentification) {
3050
+ // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
3051
+ return new UnexpectedError(spaceTrim$1((block) => `
3052
+ Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2532
3053
 
2533
- ${block(pipelineIdentification)}
2534
- `));
2535
- }
2536
- const currentlyResovedTasks = unresovedTasks.filter((task) => task.dependentParameterNames.every((name) => resovedParameters.includes(name)));
2537
- if (currentlyResovedTasks.length === 0) {
2538
- throw new PipelineLogicError(
2539
- // TODO: [🐎] DRY
2540
- spaceTrim$1((block) => `
3054
+ ${block(pipelineIdentification)}
3055
+ `));
3056
+ }
3057
+ /**
3058
+ * Creates the detailed error for unresolved or circular task dependencies.
3059
+ *
3060
+ * @private internal utility of `validatePipeline`
3061
+ */
3062
+ function createUnresolvedTasksError(unresolvedTasks, resolvedParameterNames, pipelineIdentification) {
3063
+ return new PipelineLogicError(
3064
+ // TODO: [🐎] DRY
3065
+ spaceTrim$1((block) => `
2541
3066
 
2542
- Can not resolve some parameters:
2543
- Either you are using a parameter that is not defined, or there are some circular dependencies.
3067
+ Can not resolve some parameters:
3068
+ Either you are using a parameter that is not defined, or there are some circular dependencies.
2544
3069
 
2545
- ${block(pipelineIdentification)}
3070
+ ${block(pipelineIdentification)}
2546
3071
 
2547
- **Can not resolve:**
2548
- ${block(unresovedTasks
2549
- .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2550
- .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2551
- .join(' and ')}`)
2552
- .join('\n'))}
3072
+ **Can not resolve:**
3073
+ ${block(unresolvedTasks
3074
+ .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
3075
+ .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
3076
+ .join(' and ')}`)
3077
+ .join('\n'))}
2553
3078
 
2554
- **Resolved:**
2555
- ${block(resovedParameters
2556
- .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2557
- .map((name) => `- Parameter \`{${name}}\``)
2558
- .join('\n'))}
3079
+ **Resolved:**
3080
+ ${block(resolvedParameterNames
3081
+ .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
3082
+ .map((name) => `- Parameter \`{${name}}\``)
3083
+ .join('\n'))}
2559
3084
 
2560
3085
 
2561
- **Reserved (which are available):**
2562
- ${block(resovedParameters
2563
- .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2564
- .map((name) => `- Parameter \`{${name}}\``)
2565
- .join('\n'))}
3086
+ **Reserved (which are available):**
3087
+ ${block(resolvedParameterNames
3088
+ .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
3089
+ .map((name) => `- Parameter \`{${name}}\``)
3090
+ .join('\n'))}
2566
3091
 
2567
3092
 
2568
- `));
2569
- }
2570
- resovedParameters = [
2571
- ...resovedParameters,
2572
- ...currentlyResovedTasks.map(({ resultingParameterName }) => resultingParameterName),
2573
- ];
2574
- unresovedTasks = unresovedTasks.filter((task) => !currentlyResovedTasks.includes(task));
2575
- }
2576
- // Note: Check that formfactor is corresponding to the pipeline interface
2577
- // TODO: !!6 Implement this
2578
- // pipeline.formfactorName
3093
+ `));
2579
3094
  }
2580
3095
  /**
2581
3096
  * TODO: [🧞‍♀️] Do not allow joker + foreach
@@ -3248,70 +3763,281 @@ function assertsTaskSuccessful(executionResult) {
3248
3763
  // TODO: [🧠] Can this return type be better typed than void
3249
3764
 
3250
3765
  /**
3251
- * Helper to create a new task
3766
+ * Resolves the short task summary shown in the UI.
3767
+ *
3768
+ * @private internal helper function of `ExecutionTask`
3769
+ */
3770
+ function resolveTaskTldr(options) {
3771
+ const { customTldr } = options;
3772
+ if (customTldr) {
3773
+ return customTldr;
3774
+ }
3775
+ return {
3776
+ percent: resolveTaskPercent(options),
3777
+ message: `${resolveTaskMessage(options)} (!!!fallback)`,
3778
+ };
3779
+ }
3780
+ /**
3781
+ * Resolves the best progress percentage for the current task state.
3782
+ *
3783
+ * @private internal helper function of `ExecutionTask`
3784
+ */
3785
+ function resolveTaskPercent(options) {
3786
+ const explicitPercent = getExplicitTaskPercent(options.currentValue);
3787
+ if (typeof explicitPercent === 'number') {
3788
+ return normalizeTaskPercent(explicitPercent);
3789
+ }
3790
+ return normalizeTaskPercent(calculateSimulatedTaskPercent(options));
3791
+ }
3792
+ /**
3793
+ * Picks a directly reported progress percentage from the task result snapshot.
3794
+ *
3795
+ * @private internal helper function of `ExecutionTask`
3796
+ */
3797
+ function getExplicitTaskPercent(currentValue) {
3798
+ var _a, _b, _c, _d, _e, _f;
3799
+ 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);
3800
+ }
3801
+ /**
3802
+ * Simulates progress when the task result does not expose an explicit percentage.
3803
+ *
3804
+ * @private internal helper function of `ExecutionTask`
3805
+ */
3806
+ function calculateSimulatedTaskPercent(options) {
3807
+ const { currentValue, status, createdAt } = options;
3808
+ const elapsedMs = new Date().getTime() - createdAt.getTime();
3809
+ const timeProgress = Math.min(elapsedMs / DEFAULT_TASK_SIMULATED_DURATION_MS, 1);
3810
+ const { subtaskCount, completedSubtasks } = summarizeTaskSubtasks(currentValue);
3811
+ if (status === 'FINISHED') {
3812
+ return 1;
3813
+ }
3814
+ if (status === 'ERROR') {
3815
+ return 0;
3816
+ }
3817
+ return Math.min(completedSubtasks / subtaskCount + (1 / subtaskCount) * timeProgress, 1);
3818
+ }
3819
+ /**
3820
+ * Counts total and completed subtasks used by the fallback progress simulation.
3821
+ *
3822
+ * @private internal helper function of `ExecutionTask`
3823
+ */
3824
+ function summarizeTaskSubtasks(currentValue) {
3825
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks)) {
3826
+ return { subtaskCount: 1, completedSubtasks: 0 };
3827
+ }
3828
+ return {
3829
+ subtaskCount: currentValue.subtasks.length || 1,
3830
+ completedSubtasks: currentValue.subtasks.filter(isTaskSubtaskCompleted).length,
3831
+ };
3832
+ }
3833
+ /**
3834
+ * Tells whether a task subtask is already finished.
3835
+ *
3836
+ * @private internal helper function of `ExecutionTask`
3837
+ */
3838
+ function isTaskSubtaskCompleted(subtask) {
3839
+ return subtask.done || subtask.completed || false;
3840
+ }
3841
+ /**
3842
+ * Normalizes a progress percentage into the expected `0..1` range.
3843
+ *
3844
+ * @private internal helper function of `ExecutionTask`
3845
+ */
3846
+ function normalizeTaskPercent(percentRaw) {
3847
+ let percent = Number(percentRaw) || 0;
3848
+ if (percent < 0) {
3849
+ percent = 0;
3850
+ }
3851
+ if (percent > 1) {
3852
+ percent = 1;
3853
+ }
3854
+ return percent;
3855
+ }
3856
+ /**
3857
+ * Resolves the best human-readable status message for the current task state.
3858
+ *
3859
+ * @private internal helper function of `ExecutionTask`
3860
+ */
3861
+ function resolveTaskMessage(options) {
3862
+ return (getCurrentValueMessage(options.currentValue) ||
3863
+ getCurrentSubtaskMessage(options.currentValue) ||
3864
+ getLatestIssueMessage(options.errors, 'Error') ||
3865
+ getLatestIssueMessage(options.warnings, 'Warning') ||
3866
+ getStatusMessage(options.status));
3867
+ }
3868
+ /**
3869
+ * Picks a message already reported by the current task result snapshot.
3870
+ *
3871
+ * @private internal helper function of `ExecutionTask`
3872
+ */
3873
+ function getCurrentValueMessage(currentValue) {
3874
+ var _a, _b, _c, _d;
3875
+ 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;
3876
+ }
3877
+ /**
3878
+ * Builds a fallback message from the first unfinished subtask title.
3879
+ *
3880
+ * @private internal helper function of `ExecutionTask`
3881
+ */
3882
+ function getCurrentSubtaskMessage(currentValue) {
3883
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks) || currentValue.subtasks.length === 0) {
3884
+ return undefined;
3885
+ }
3886
+ const currentSubtask = currentValue.subtasks.find((subtask) => !isTaskSubtaskCompleted(subtask));
3887
+ if (!(currentSubtask === null || currentSubtask === void 0 ? void 0 : currentSubtask.title)) {
3888
+ return undefined;
3889
+ }
3890
+ return `Working on ${currentSubtask.title}`;
3891
+ }
3892
+ /**
3893
+ * Picks the latest error or warning message, with the legacy generic fallback label.
3894
+ *
3895
+ * @private internal helper function of `ExecutionTask`
3896
+ */
3897
+ function getLatestIssueMessage(issues, fallbackMessage) {
3898
+ if (issues.length === 0) {
3899
+ return undefined;
3900
+ }
3901
+ return issues[issues.length - 1].message || fallbackMessage;
3902
+ }
3903
+ /**
3904
+ * Builds the final status-based fallback message.
3905
+ *
3906
+ * @private internal helper function of `ExecutionTask`
3907
+ */
3908
+ function getStatusMessage(status) {
3909
+ if (status === 'FINISHED') {
3910
+ return 'Finished';
3911
+ }
3912
+ if (status === 'ERROR') {
3913
+ return 'Error';
3914
+ }
3915
+ return 'Running';
3916
+ }
3917
+
3918
+ /**
3919
+ * Creates the initial mutable state for a task.
3252
3920
  *
3253
3921
  * @private internal helper function
3254
3922
  */
3255
- function createTask(options) {
3256
- const { taskType, taskProcessCallback } = options;
3257
- let { title } = options;
3258
- // TODO: [🐙] DRY
3259
- const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3260
- let status = 'RUNNING';
3261
- const createdAt = new Date();
3262
- let updatedAt = createdAt;
3263
- const errors = [];
3264
- const warnings = [];
3265
- const llmCalls = [];
3266
- let currentValue = {};
3267
- let customTldr = null;
3268
- const partialResultSubject = new Subject();
3269
- // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3270
- const finalResultPromise = /* not await */ taskProcessCallback((newOngoingResult) => {
3923
+ function createTaskState(title, createdAt) {
3924
+ return {
3925
+ title,
3926
+ status: 'RUNNING',
3927
+ updatedAt: createdAt,
3928
+ errors: [],
3929
+ warnings: [],
3930
+ llmCalls: [],
3931
+ currentValue: {},
3932
+ customTldr: null,
3933
+ };
3934
+ }
3935
+ /**
3936
+ * Creates the partial-result updater passed into the task process callback.
3937
+ *
3938
+ * @private internal helper function
3939
+ */
3940
+ function createOngoingResultUpdater(taskState, partialResultSubject) {
3941
+ return (newOngoingResult) => {
3271
3942
  if (newOngoingResult.title) {
3272
- title = newOngoingResult.title;
3943
+ taskState.title = newOngoingResult.title;
3273
3944
  }
3274
- updatedAt = new Date();
3275
- Object.assign(currentValue, newOngoingResult);
3945
+ taskState.updatedAt = new Date();
3946
+ Object.assign(taskState.currentValue, newOngoingResult);
3276
3947
  // <- TODO: assign deep
3277
3948
  partialResultSubject.next(newOngoingResult);
3278
- }, (tldrInfo) => {
3279
- customTldr = tldrInfo;
3280
- updatedAt = new Date();
3281
- }, (llmCall) => {
3282
- llmCalls.push(llmCall);
3283
- updatedAt = new Date();
3284
- });
3949
+ };
3950
+ }
3951
+ /**
3952
+ * Creates the custom-TLDR updater passed into the task process callback.
3953
+ *
3954
+ * @private internal helper function
3955
+ */
3956
+ function createTldrUpdater(taskState) {
3957
+ return (tldrInfo) => {
3958
+ taskState.customTldr = tldrInfo;
3959
+ taskState.updatedAt = new Date();
3960
+ };
3961
+ }
3962
+ /**
3963
+ * Creates the LLM call logger passed into the task process callback.
3964
+ *
3965
+ * @private internal helper function
3966
+ */
3967
+ function createLlmCallLogger(taskState) {
3968
+ return (llmCall) => {
3969
+ taskState.llmCalls.push(llmCall);
3970
+ taskState.updatedAt = new Date();
3971
+ };
3972
+ }
3973
+ /**
3974
+ * Wires the task promise into the observable/error lifecycle.
3975
+ *
3976
+ * @private internal helper function
3977
+ */
3978
+ function settleTaskPromise(finalResultPromise, taskState, partialResultSubject) {
3285
3979
  finalResultPromise
3286
3980
  .catch((error) => {
3287
- errors.push(error);
3981
+ taskState.errors.push(error);
3288
3982
  partialResultSubject.error(error);
3289
3983
  })
3290
3984
  .then((executionResult) => {
3291
3985
  if (executionResult) {
3292
3986
  try {
3293
- updatedAt = new Date();
3294
- errors.push(...executionResult.errors);
3295
- warnings.push(...executionResult.warnings);
3296
- // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3297
- // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3298
- // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3299
- // And delete `ExecutionTask.currentValue.preparedPipeline`
3300
- assertsTaskSuccessful(executionResult);
3301
- status = 'FINISHED';
3302
- currentValue = jsonStringsToJsons(executionResult);
3303
- // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3304
- partialResultSubject.next(executionResult);
3987
+ finalizeTaskResult(executionResult, taskState, partialResultSubject);
3305
3988
  }
3306
3989
  catch (error) {
3307
- assertsError(error);
3308
- status = 'ERROR';
3309
- errors.push(error);
3310
- partialResultSubject.error(error);
3990
+ failTaskResult(error, taskState, partialResultSubject);
3311
3991
  }
3312
3992
  }
3313
3993
  partialResultSubject.complete();
3314
3994
  });
3995
+ }
3996
+ /**
3997
+ * Applies the final successful task result into the mutable task state.
3998
+ *
3999
+ * @private internal helper function
4000
+ */
4001
+ function finalizeTaskResult(executionResult, taskState, partialResultSubject) {
4002
+ taskState.updatedAt = new Date();
4003
+ taskState.errors.push(...executionResult.errors);
4004
+ taskState.warnings.push(...executionResult.warnings);
4005
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
4006
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
4007
+ // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
4008
+ // And delete `ExecutionTask.currentValue.preparedPipeline`
4009
+ assertsTaskSuccessful(executionResult);
4010
+ taskState.status = 'FINISHED';
4011
+ taskState.currentValue = jsonStringsToJsons(executionResult);
4012
+ // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
4013
+ partialResultSubject.next(executionResult);
4014
+ }
4015
+ /**
4016
+ * Records a final-result failure after the task promise itself resolved.
4017
+ *
4018
+ * @private internal helper function
4019
+ */
4020
+ function failTaskResult(error, taskState, partialResultSubject) {
4021
+ assertsError(error);
4022
+ taskState.status = 'ERROR';
4023
+ taskState.errors.push(error);
4024
+ partialResultSubject.error(error);
4025
+ }
4026
+ /**
4027
+ * Helper to create a new task
4028
+ *
4029
+ * @private internal helper function
4030
+ */
4031
+ function createTask(options) {
4032
+ const { taskType, title, taskProcessCallback } = options;
4033
+ // TODO: [🐙] DRY
4034
+ const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
4035
+ const createdAt = new Date();
4036
+ const taskState = createTaskState(title, createdAt);
4037
+ const partialResultSubject = new Subject();
4038
+ // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
4039
+ const finalResultPromise = /* not await */ taskProcessCallback(createOngoingResultUpdater(taskState, partialResultSubject), createTldrUpdater(taskState), createLlmCallLogger(taskState));
4040
+ settleTaskPromise(finalResultPromise, taskState, partialResultSubject);
3315
4041
  async function asPromise(options) {
3316
4042
  const { isCrashedOnError = true } = options || {};
3317
4043
  const finalResult = await finalResultPromise;
@@ -3327,91 +4053,29 @@ function createTask(options) {
3327
4053
  return PROMPTBOOK_ENGINE_VERSION;
3328
4054
  },
3329
4055
  get title() {
3330
- return title;
4056
+ return taskState.title;
3331
4057
  // <- Note: [1] These must be getters to allow changing the value in the future
3332
4058
  },
3333
4059
  get status() {
3334
- return status;
4060
+ return taskState.status;
3335
4061
  // <- Note: [1] --||--
3336
4062
  },
3337
4063
  get tldr() {
3338
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3339
- // Use custom tldr if available
3340
- if (customTldr) {
3341
- return customTldr;
3342
- }
3343
- // Fallback to default implementation
3344
- const cv = currentValue;
3345
- // If explicit percent is provided, use it
3346
- 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;
3347
- // Simulate progress if not provided
3348
- if (typeof percentRaw !== 'number') {
3349
- // Simulate progress: evenly split across subtasks, based on elapsed time
3350
- const now = new Date();
3351
- const elapsedMs = now.getTime() - createdAt.getTime();
3352
- const totalMs = DEFAULT_TASK_SIMULATED_DURATION_MS;
3353
- // If subtasks are defined, split progress evenly
3354
- const subtaskCount = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) ? cv.subtasks.length : 1;
3355
- const completedSubtasks = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks)
3356
- ? cv.subtasks.filter((s) => s.done || s.completed).length
3357
- : 0;
3358
- // Progress from completed subtasks
3359
- const subtaskProgress = subtaskCount > 0 ? completedSubtasks / subtaskCount : 0;
3360
- // Progress from elapsed time for current subtask
3361
- const timeProgress = Math.min(elapsedMs / totalMs, 1);
3362
- // Combine: completed subtasks + time progress for current subtask
3363
- percentRaw = Math.min(subtaskProgress + (1 / subtaskCount) * timeProgress, 1);
3364
- if (status === 'FINISHED')
3365
- percentRaw = 1;
3366
- if (status === 'ERROR')
3367
- percentRaw = 0;
3368
- }
3369
- // Clamp to [0,1]
3370
- let percent = Number(percentRaw) || 0;
3371
- if (percent < 0)
3372
- percent = 0;
3373
- if (percent > 1)
3374
- percent = 1;
3375
- // Build a short message: prefer explicit tldr.message, then common summary/message fields, then errors/warnings, then status
3376
- 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;
3377
- let message = messageFromResult;
3378
- if (!message) {
3379
- // If subtasks, show current subtask
3380
- if (Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) && cv.subtasks.length > 0) {
3381
- const current = cv.subtasks.find((s) => !s.done && !s.completed);
3382
- if (current && current.title) {
3383
- message = `Working on ${current.title}`;
3384
- }
3385
- }
3386
- if (!message) {
3387
- if (errors.length) {
3388
- message = errors[errors.length - 1].message || 'Error';
3389
- }
3390
- else if (warnings.length) {
3391
- message = warnings[warnings.length - 1].message || 'Warning';
3392
- }
3393
- else if (status === 'FINISHED') {
3394
- message = 'Finished';
3395
- }
3396
- else if (status === 'ERROR') {
3397
- message = 'Error';
3398
- }
3399
- else {
3400
- message = 'Running';
3401
- }
3402
- }
3403
- }
3404
- return {
3405
- percent: percent,
3406
- message: message + ' (!!!fallback)',
3407
- };
4064
+ return resolveTaskTldr({
4065
+ customTldr: taskState.customTldr,
4066
+ currentValue: taskState.currentValue,
4067
+ status: taskState.status,
4068
+ createdAt,
4069
+ errors: taskState.errors,
4070
+ warnings: taskState.warnings,
4071
+ });
3408
4072
  },
3409
4073
  get createdAt() {
3410
4074
  return createdAt;
3411
4075
  // <- Note: [1] --||--
3412
4076
  },
3413
4077
  get updatedAt() {
3414
- return updatedAt;
4078
+ return taskState.updatedAt;
3415
4079
  // <- Note: [1] --||--
3416
4080
  },
3417
4081
  asPromise,
@@ -3419,19 +4083,19 @@ function createTask(options) {
3419
4083
  return partialResultSubject.asObservable();
3420
4084
  },
3421
4085
  get errors() {
3422
- return errors;
4086
+ return taskState.errors;
3423
4087
  // <- Note: [1] --||--
3424
4088
  },
3425
4089
  get warnings() {
3426
- return warnings;
4090
+ return taskState.warnings;
3427
4091
  // <- Note: [1] --||--
3428
4092
  },
3429
4093
  get llmCalls() {
3430
- return [...llmCalls, { foo: '!!! bar' }];
4094
+ return [...taskState.llmCalls, { foo: '!!! bar' }];
3431
4095
  // <- Note: [1] --||--
3432
4096
  },
3433
4097
  get currentValue() {
3434
- return currentValue;
4098
+ return taskState.currentValue;
3435
4099
  // <- Note: [1] --||--
3436
4100
  },
3437
4101
  };
@@ -4599,210 +5263,275 @@ const promptbookFetch = async (urlOrRequest, init) => {
4599
5263
  * @public exported from `@promptbook/core`
4600
5264
  */
4601
5265
  async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
5266
+ const { knowledgeSourceContent } = knowledgeSource;
5267
+ const name = knowledgeSource.name || knowledgeSourceContentToName(knowledgeSourceContent);
5268
+ const { rootDirname = null, isVerbose = DEFAULT_IS_VERBOSE } = options || {};
5269
+ if (isValidUrl(knowledgeSourceContent)) {
5270
+ return makeUrlKnowledgeSourceHandler(knowledgeSourceContent, name, tools, options, isVerbose);
5271
+ }
5272
+ if (isValidFilePath(knowledgeSourceContent)) {
5273
+ return makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose);
5274
+ }
5275
+ return makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose);
5276
+ }
5277
+ /**
5278
+ * Creates a source handler for URL-based knowledge.
5279
+ *
5280
+ * @private internal utility of `makeKnowledgeSourceHandler`
5281
+ */
5282
+ async function makeUrlKnowledgeSourceHandler(url, name, tools, options, isVerbose) {
4602
5283
  var _a;
4603
5284
  const { fetch = promptbookFetch } = tools;
4604
- const { knowledgeSourceContent } = knowledgeSource;
4605
- let { name } = knowledgeSource;
4606
- const { rootDirname = null,
4607
- // <- TODO: process.cwd() if running in Node.js
4608
- isVerbose = DEFAULT_IS_VERBOSE, } = options || {};
4609
- if (!name) {
4610
- name = knowledgeSourceContentToName(knowledgeSourceContent);
5285
+ if (isVerbose) {
5286
+ console.info(`📄 [1] "${name}" is available at "${url}"`);
4611
5287
  }
4612
- if (isValidUrl(knowledgeSourceContent)) {
4613
- const url = knowledgeSourceContent;
4614
- if (isVerbose) {
4615
- console.info(`📄 [1] "${name}" is available at "${url}"`);
4616
- }
4617
- const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
4618
- const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
4619
- if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
4620
- if (isVerbose) {
4621
- console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
4622
- }
4623
- return {
4624
- source: name,
4625
- filename: null,
4626
- url,
4627
- mimeType,
4628
- /*
4629
- TODO: [🥽]
4630
- > async asBlob() {
4631
- > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
4632
- > const content = await response.blob();
4633
- > return content;
4634
- > },
4635
- */
4636
- async asJson() {
4637
- // TODO: [👨🏻‍🤝‍👨🏻]
4638
- const content = await response.json();
4639
- return content;
4640
- },
4641
- async asText() {
4642
- // TODO: [👨🏻‍🤝‍👨🏻]
4643
- const content = await response.text();
4644
- return content;
4645
- },
4646
- };
4647
- }
4648
- const basename = url.split('/').pop() || titleToName(url);
4649
- const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
4650
- // <- TODO: [🥬] Encapsulate sha256 to some private utility function
4651
- const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
4652
- const filepath = join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
4653
- // Note: Try to create cache directory, but don't fail if filesystem has issues
4654
- try {
4655
- await tools.fs.mkdir(dirname(join(rootDirname, filepath)), { recursive: true });
4656
- }
4657
- catch (error) {
4658
- if (isVerbose) {
4659
- console.info(`📄 [3] "${name}" error creating cache directory`);
4660
- }
4661
- // Note: If we can't create cache directory, we'll handle it when trying to write the file
4662
- // This handles read-only filesystems, permission issues, and missing parent directories
4663
- if (error instanceof Error &&
4664
- (error.message.includes('EROFS') ||
4665
- error.message.includes('read-only') ||
4666
- error.message.includes('EACCES') ||
4667
- error.message.includes('EPERM') ||
4668
- error.message.includes('ENOENT'))) ;
4669
- else {
4670
- // Re-throw other unexpected errors
4671
- throw error;
4672
- }
4673
- }
4674
- const fileContent = Buffer.from(await response.arrayBuffer());
4675
- if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
4676
- 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.`);
4677
- }
4678
- // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
4679
- try {
4680
- await tools.fs.writeFile(join(rootDirname, filepath), fileContent);
4681
- }
4682
- catch (error) {
4683
- if (isVerbose) {
4684
- console.info(`📄 [4] "${name}" error writing cache file`);
4685
- }
4686
- // Note: If we can't write to cache, we'll process the file directly from memory
4687
- // This handles read-only filesystems like Vercel
4688
- if (error instanceof Error &&
4689
- (error.message.includes('EROFS') ||
4690
- error.message.includes('read-only') ||
4691
- error.message.includes('EACCES') ||
4692
- error.message.includes('EPERM') ||
4693
- error.message.includes('ENOENT'))) {
4694
- // Return a handler that works directly with the downloaded content
4695
- return {
4696
- source: name,
4697
- filename: null,
4698
- url,
4699
- mimeType,
4700
- async asJson() {
4701
- return JSON.parse(fileContent.toString('utf-8'));
4702
- },
4703
- async asText() {
4704
- return fileContent.toString('utf-8');
4705
- },
4706
- };
4707
- }
4708
- else {
4709
- // Re-throw other unexpected errors
4710
- throw error;
4711
- }
4712
- }
4713
- // TODO: [💵] Check the file security
4714
- // TODO: [🧹][🧠] Delete the file after the scraping is done
4715
- if (isVerbose) {
4716
- console.info(`📄 [5] "${name}" cached at "${join(rootDirname, filepath)}"`);
4717
- }
4718
- return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
4719
- ...options,
4720
- rootDirname,
4721
- });
5288
+ const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
5289
+ const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
5290
+ if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
5291
+ return makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose);
5292
+ }
5293
+ return cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose);
5294
+ }
5295
+ /**
5296
+ * Creates a source handler that reads directly from a fetched response.
5297
+ *
5298
+ * @private internal utility of `makeKnowledgeSourceHandler`
5299
+ */
5300
+ function makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose) {
5301
+ if (isVerbose) {
5302
+ console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
5303
+ }
5304
+ return {
5305
+ source: name,
5306
+ filename: null,
5307
+ url,
5308
+ mimeType,
5309
+ /*
5310
+ TODO: [🥽]
5311
+ > async asBlob() {
5312
+ > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
5313
+ > const content = await response.blob();
5314
+ > return content;
5315
+ > },
5316
+ */
5317
+ async asJson() {
5318
+ // TODO: [👨🏻‍🤝‍👨🏻]
5319
+ const content = await response.json();
5320
+ return content;
5321
+ },
5322
+ async asText() {
5323
+ // TODO: [👨🏻‍🤝‍👨🏻]
5324
+ const content = await response.text();
5325
+ return content;
5326
+ },
5327
+ };
5328
+ }
5329
+ /**
5330
+ * Downloads a PDF knowledge source into cache when possible and falls back to in-memory content otherwise.
5331
+ *
5332
+ * @private internal utility of `makeKnowledgeSourceHandler`
5333
+ */
5334
+ async function cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose) {
5335
+ const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5336
+ const filepath = createDownloadedKnowledgeSourceFilepath(url, mimeType);
5337
+ const fullFilepath = join(rootDirname, filepath);
5338
+ await createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose);
5339
+ const fileContent = Buffer.from(await response.arrayBuffer());
5340
+ if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5341
+ 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.`);
5342
+ }
5343
+ const isCached = await writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose);
5344
+ if (!isCached) {
5345
+ return makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent);
5346
+ }
5347
+ // TODO: [💵] Check the file security
5348
+ // TODO: [🧹][🧠] Delete the file after the scraping is done
5349
+ if (isVerbose) {
5350
+ console.info(`📄 [5] "${name}" cached at "${fullFilepath}"`);
5351
+ }
5352
+ return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
5353
+ ...options,
5354
+ rootDirname,
5355
+ });
5356
+ }
5357
+ /**
5358
+ * Builds a stable cache filepath for a downloaded knowledge source.
5359
+ *
5360
+ * @private internal utility of `makeKnowledgeSourceHandler`
5361
+ */
5362
+ function createDownloadedKnowledgeSourceFilepath(url, mimeType) {
5363
+ const basename = url.split('/').pop() || titleToName(url);
5364
+ const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
5365
+ // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5366
+ return join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
5367
+ }
5368
+ /**
5369
+ * Tries to create the cache directory needed for a downloaded knowledge source.
5370
+ *
5371
+ * @private internal utility of `makeKnowledgeSourceHandler`
5372
+ */
5373
+ async function createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose) {
5374
+ try {
5375
+ await tools.fs.mkdir(dirname(fullFilepath), { recursive: true });
4722
5376
  }
4723
- else if (isValidFilePath(knowledgeSourceContent)) {
4724
- if (tools.fs === undefined) {
4725
- throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
4726
- // <- TODO: [🧠] What is the best error type here`
5377
+ catch (error) {
5378
+ if (isVerbose) {
5379
+ console.info(`📄 [3] "${name}" error creating cache directory`);
4727
5380
  }
4728
- if (rootDirname === null) {
4729
- throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
4730
- // <- TODO: [🧠] What is the best error type here`
5381
+ // Note: If we can't create cache directory, we'll handle it when trying to write the file
5382
+ // This handles read-only filesystems, permission issues, and missing parent directories
5383
+ if (!isIgnorableCacheFilesystemError(error)) {
5384
+ throw error;
4731
5385
  }
4732
- const filename = isAbsolute(knowledgeSourceContent)
4733
- ? knowledgeSourceContent
4734
- : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5386
+ }
5387
+ }
5388
+ /**
5389
+ * Tries to write downloaded content into cache and reports whether the cache was usable.
5390
+ *
5391
+ * @private internal utility of `makeKnowledgeSourceHandler`
5392
+ */
5393
+ async function writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose) {
5394
+ // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
5395
+ try {
5396
+ await tools.fs.writeFile(fullFilepath, fileContent);
5397
+ return true;
5398
+ }
5399
+ catch (error) {
4735
5400
  if (isVerbose) {
4736
- console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5401
+ console.info(`📄 [4] "${name}" error writing cache file`);
4737
5402
  }
4738
- const fileExtension = getFileExtension(filename);
4739
- const mimeType = extensionToMimeType(fileExtension || '');
4740
- if (!(await isFileExisting(filename, tools.fs))) {
4741
- throw new NotFoundError(spaceTrim$1((block) => `
4742
- Can not make source handler for file which does not exist:
5403
+ // Note: If we can't write to cache, we'll process the file directly from memory
5404
+ // This handles read-only filesystems like Vercel
5405
+ if (isIgnorableCacheFilesystemError(error)) {
5406
+ return false;
5407
+ }
5408
+ throw error;
5409
+ }
5410
+ }
5411
+ /**
5412
+ * Detects filesystem errors that should not fail optional caching.
5413
+ *
5414
+ * @private internal utility of `makeKnowledgeSourceHandler`
5415
+ */
5416
+ function isIgnorableCacheFilesystemError(error) {
5417
+ return (error instanceof Error &&
5418
+ (error.message.includes('EROFS') ||
5419
+ error.message.includes('read-only') ||
5420
+ error.message.includes('EACCES') ||
5421
+ error.message.includes('EPERM') ||
5422
+ error.message.includes('ENOENT')));
5423
+ }
5424
+ /**
5425
+ * Creates a source handler backed by already downloaded file content kept in memory.
5426
+ *
5427
+ * @private internal utility of `makeKnowledgeSourceHandler`
5428
+ */
5429
+ function makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent) {
5430
+ return {
5431
+ source: name,
5432
+ filename: null,
5433
+ url,
5434
+ mimeType,
5435
+ async asJson() {
5436
+ return JSON.parse(fileContent.toString('utf-8'));
5437
+ },
5438
+ async asText() {
5439
+ return fileContent.toString('utf-8');
5440
+ },
5441
+ };
5442
+ }
5443
+ /**
5444
+ * Creates a source handler for file-based knowledge.
5445
+ *
5446
+ * @private internal utility of `makeKnowledgeSourceHandler`
5447
+ */
5448
+ async function makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose) {
5449
+ if (tools.fs === undefined) {
5450
+ throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
5451
+ // <- TODO: [🧠] What is the best error type here`
5452
+ }
5453
+ if (rootDirname === null) {
5454
+ throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
5455
+ // <- TODO: [🧠] What is the best error type here`
5456
+ }
5457
+ const filename = isAbsolute(knowledgeSourceContent)
5458
+ ? knowledgeSourceContent
5459
+ : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5460
+ if (isVerbose) {
5461
+ console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5462
+ }
5463
+ const fileExtension = getFileExtension(filename);
5464
+ const mimeType = extensionToMimeType(fileExtension || '');
5465
+ if (!(await isFileExisting(filename, tools.fs))) {
5466
+ throw new NotFoundError(spaceTrim$1((block) => `
5467
+ Can not make source handler for file which does not exist:
4743
5468
 
4744
- File:
4745
- ${block(knowledgeSourceContent)}
5469
+ File:
5470
+ ${block(knowledgeSourceContent)}
4746
5471
 
4747
- Full file path:
4748
- ${block(filename)}
4749
- `));
4750
- }
4751
- // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
4752
- return {
4753
- source: name,
4754
- filename,
4755
- url: null,
4756
- mimeType,
4757
- /*
4758
- TODO: [🥽]
4759
- > async asBlob() {
4760
- > const content = await tools.fs!.readFile(filename);
4761
- > return new Blob(
4762
- > [
4763
- > content,
4764
- > // <- TODO: [🥽] This is NOT tested, test it
4765
- > ],
4766
- > { type: mimeType },
4767
- > );
4768
- > },
4769
- */
4770
- async asJson() {
4771
- return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
4772
- },
4773
- async asText() {
4774
- return await tools.fs.readFile(filename, 'utf-8');
4775
- },
4776
- };
5472
+ Full file path:
5473
+ ${block(filename)}
5474
+ `));
4777
5475
  }
4778
- else {
4779
- if (isVerbose) {
4780
- console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
4781
- console.info('---');
4782
- console.info(knowledgeSourceContent);
4783
- console.info('---');
4784
- }
4785
- return {
4786
- source: name,
4787
- filename: null,
4788
- url: null,
4789
- mimeType: 'text/markdown',
4790
- asText() {
4791
- return knowledgeSource.knowledgeSourceContent;
4792
- },
4793
- asJson() {
4794
- throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
4795
- },
4796
- /*
4797
- TODO: [🥽]
4798
- > asBlob() {
4799
- > throw new UnexpectedError(
4800
- > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
4801
- > );
4802
- > },
4803
- */
4804
- };
5476
+ // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
5477
+ return {
5478
+ source: name,
5479
+ filename,
5480
+ url: null,
5481
+ mimeType,
5482
+ /*
5483
+ TODO: [🥽]
5484
+ > async asBlob() {
5485
+ > const content = await tools.fs!.readFile(filename);
5486
+ > return new Blob(
5487
+ > [
5488
+ > content,
5489
+ > // <- TODO: [🥽] This is NOT tested, test it
5490
+ > ],
5491
+ > { type: mimeType },
5492
+ > );
5493
+ > },
5494
+ */
5495
+ async asJson() {
5496
+ return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
5497
+ },
5498
+ async asText() {
5499
+ return await tools.fs.readFile(filename, 'utf-8');
5500
+ },
5501
+ };
5502
+ }
5503
+ /**
5504
+ * Creates a source handler for inline text knowledge.
5505
+ *
5506
+ * @private internal utility of `makeKnowledgeSourceHandler`
5507
+ */
5508
+ function makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose) {
5509
+ if (isVerbose) {
5510
+ console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
5511
+ console.info('---');
5512
+ console.info(knowledgeSourceContent);
5513
+ console.info('---');
4805
5514
  }
5515
+ return {
5516
+ source: name,
5517
+ filename: null,
5518
+ url: null,
5519
+ mimeType: 'text/markdown',
5520
+ asText() {
5521
+ return knowledgeSourceContent;
5522
+ },
5523
+ asJson() {
5524
+ throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
5525
+ },
5526
+ /*
5527
+ TODO: [🥽]
5528
+ > asBlob() {
5529
+ > throw new UnexpectedError(
5530
+ > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
5531
+ > );
5532
+ > },
5533
+ */
5534
+ };
4806
5535
  }
4807
5536
 
4808
5537
  /**
@@ -5995,204 +6724,102 @@ const CountUtils = {
5995
6724
  // TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
5996
6725
  // Note: [💞] Ignore a discrepancy between file name and entity name
5997
6726
 
5998
- /**
5999
- * Function checkExpectations will check if the expectations on given value are met
6000
- *
6001
- * Note: There are two similar functions:
6002
- * - `checkExpectations` which throws an error if the expectations are not met
6003
- * - `isPassingExpectations` which returns a boolean
6004
- *
6005
- * @throws {ExpectError} if the expectations are not met
6006
- * @returns {void} Nothing
6007
- *
6008
- * @private internal function of `createPipelineExecutor`
6009
- */
6010
- function checkExpectations(expectations, value) {
6011
- for (const [unit, { max, min }] of Object.entries(expectations)) {
6012
- const amount = CountUtils[unit.toUpperCase()](value);
6013
- if (min && amount < min) {
6014
- throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6015
- } /* not else */
6016
- if (max && amount > max) {
6017
- throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6018
- }
6019
- }
6020
- }
6021
- // TODO: [💝] Unite object for expecting amount and format
6022
- // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6023
- // Note: [💝] and [🤠] are interconnected together
6024
-
6025
- /**
6026
- * Validates a prompt result against expectations and format requirements.
6027
- * This function provides a common abstraction for result validation that can be used
6028
- * by both execution logic and caching logic to ensure consistency.
6029
- *
6030
- * Note: [🔂] This function is idempotent.
6031
- *
6032
- * @param options - The validation options including result string, expectations, and format
6033
- * @returns Validation result with processed string and validity status
6034
- *
6035
- * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6036
- */
6037
- function validatePromptResult(options) {
6038
- const { resultString, expectations, format } = options;
6039
- let processedResultString = resultString;
6040
- let validationError;
6041
- try {
6042
- // TODO: [💝] Unite object for expecting amount and format
6043
- if (format) {
6044
- if (format === 'JSON') {
6045
- if (!isValidJsonString(processedResultString)) {
6046
- // TODO: [🏢] Do more universally via `FormatParser`
6047
- try {
6048
- processedResultString = extractJsonBlock(processedResultString);
6049
- }
6050
- catch (error) {
6051
- keepUnused(error);
6052
- throw new ExpectError(spaceTrim$1((block) => `
6053
- Expected valid JSON string
6054
-
6055
- The expected JSON text:
6056
- ${block(processedResultString)}
6057
- `));
6058
- }
6059
- }
6060
- }
6061
- else {
6062
- throw new UnexpectedError(`Unknown format "${format}"`);
6063
- }
6064
- }
6065
- // TODO: [💝] Unite object for expecting amount and format
6066
- if (expectations) {
6067
- checkExpectations(expectations, processedResultString);
6068
- }
6069
- return {
6070
- isValid: true,
6071
- processedResultString,
6072
- };
6073
- }
6074
- catch (error) {
6075
- if (error instanceof ExpectError) {
6076
- validationError = error;
6077
- }
6078
- else {
6079
- // Re-throw non-ExpectError errors (like UnexpectedError)
6080
- throw error;
6081
- }
6082
- return {
6083
- isValid: false,
6084
- processedResultString,
6085
- error: validationError,
6086
- };
6087
- }
6088
- }
6089
-
6090
- /**
6091
- * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
6092
- * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
6093
- * Throws errors if execution fails after all attempts.
6094
- *
6095
- * @param options - The options for execution, including task, parameters, pipeline, and configuration.
6096
- * @returns The result string of the executed task.
6097
- *
6098
- * @private internal utility of `createPipelineExecutor`
6099
- */
6100
- async function executeAttempts(options) {
6101
- const $ongoingTaskResult = createOngoingTaskResult();
6102
- const llmTools = getSingleLlmExecutionTools(options.tools.llm);
6103
- attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
6104
- const attempt = createAttemptDescriptor({
6105
- attemptIndex,
6106
- jokerParameterNames: options.jokerParameterNames,
6107
- pipelineIdentification: options.pipelineIdentification,
6108
- });
6109
- resetAttemptExecutionState($ongoingTaskResult);
6110
- try {
6111
- await executeSingleAttempt({
6112
- attempt,
6113
- options,
6114
- llmTools,
6115
- $ongoingTaskResult,
6116
- });
6117
- break attempts;
6118
- }
6119
- catch (error) {
6120
- if (!(error instanceof ExpectError)) {
6121
- throw error;
6122
- }
6123
- recordFailedAttempt({
6124
- error,
6125
- attemptIndex,
6126
- onProgress: options.onProgress,
6127
- $ongoingTaskResult,
6128
- });
6129
- }
6130
- finally {
6131
- reportPromptExecution({
6132
- attempt,
6133
- task: options.task,
6134
- $executionReport: options.$executionReport,
6135
- logLlmCall: options.logLlmCall,
6136
- $ongoingTaskResult,
6137
- });
6138
- }
6139
- throwIfFinalAttemptFailed({
6140
- attemptIndex,
6141
- maxAttempts: options.maxAttempts,
6142
- maxExecutionAttempts: options.maxExecutionAttempts,
6143
- pipelineIdentification: options.pipelineIdentification,
6144
- $ongoingTaskResult,
6145
- });
6146
- }
6147
- return getSuccessfulResultString({
6148
- pipelineIdentification: options.pipelineIdentification,
6149
- $ongoingTaskResult,
6150
- });
6151
- }
6152
- /**
6153
- * Creates mutable attempt state for one task execution lifecycle.
6154
- */
6155
- function createOngoingTaskResult() {
6156
- return {
6157
- $result: null,
6158
- $resultString: null,
6159
- $expectError: null,
6160
- $scriptPipelineExecutionErrors: [],
6161
- $failedResults: [],
6162
- };
6163
- }
6164
- /**
6165
- * Resolves the bookkeeping for one loop iteration, including joker lookup.
6166
- */
6167
- function createAttemptDescriptor(options) {
6168
- const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
6169
- const isJokerAttempt = attemptIndex < 0;
6170
- const jokerParameterName = isJokerAttempt
6171
- ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
6172
- : undefined;
6173
- if (isJokerAttempt && !jokerParameterName) {
6174
- throw new UnexpectedError(spaceTrim$1((block) => `
6175
- Joker not found in attempt ${attemptIndex}
6176
-
6177
- ${block(pipelineIdentification)}
6178
- `));
6727
+ /**
6728
+ * Function checkExpectations will check if the expectations on given value are met
6729
+ *
6730
+ * Note: There are two similar functions:
6731
+ * - `checkExpectations` which throws an error if the expectations are not met
6732
+ * - `isPassingExpectations` which returns a boolean
6733
+ *
6734
+ * @throws {ExpectError} if the expectations are not met
6735
+ * @returns {void} Nothing
6736
+ *
6737
+ * @private internal function of `createPipelineExecutor`
6738
+ */
6739
+ function checkExpectations(expectations, value) {
6740
+ for (const [unit, { max, min }] of Object.entries(expectations)) {
6741
+ const amount = CountUtils[unit.toUpperCase()](value);
6742
+ if (min && amount < min) {
6743
+ throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6744
+ } /* not else */
6745
+ if (max && amount > max) {
6746
+ throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6747
+ }
6179
6748
  }
6180
- return {
6181
- attemptIndex,
6182
- isJokerAttempt,
6183
- jokerParameterName,
6184
- };
6185
6749
  }
6750
+ // TODO: [💝] Unite object for expecting amount and format
6751
+ // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6752
+ // Note: [💝] and [🤠] are interconnected together
6753
+
6186
6754
  /**
6187
- * Clears the per-attempt result slots while preserving cumulative failure history.
6755
+ * Validates a prompt result against expectations and format requirements.
6756
+ * This function provides a common abstraction for result validation that can be used
6757
+ * by both execution logic and caching logic to ensure consistency.
6758
+ *
6759
+ * Note: [🔂] This function is idempotent.
6760
+ *
6761
+ * @param options - The validation options including result string, expectations, and format
6762
+ * @returns Validation result with processed string and validity status
6763
+ *
6764
+ * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6188
6765
  */
6189
- function resetAttemptExecutionState($ongoingTaskResult) {
6190
- $ongoingTaskResult.$result = null;
6191
- $ongoingTaskResult.$resultString = null;
6192
- $ongoingTaskResult.$expectError = null;
6766
+ function validatePromptResult(options) {
6767
+ const { resultString, expectations, format } = options;
6768
+ let processedResultString = resultString;
6769
+ let validationError;
6770
+ try {
6771
+ // TODO: [💝] Unite object for expecting amount and format
6772
+ if (format) {
6773
+ if (format === 'JSON') {
6774
+ if (!isValidJsonString(processedResultString)) {
6775
+ // TODO: [🏢] Do more universally via `FormatParser`
6776
+ try {
6777
+ processedResultString = extractJsonBlock(processedResultString);
6778
+ }
6779
+ catch (error) {
6780
+ keepUnused(error);
6781
+ throw new ExpectError(spaceTrim$1((block) => `
6782
+ Expected valid JSON string
6783
+
6784
+ The expected JSON text:
6785
+ ${block(processedResultString)}
6786
+ `));
6787
+ }
6788
+ }
6789
+ }
6790
+ else {
6791
+ throw new UnexpectedError(`Unknown format "${format}"`);
6792
+ }
6793
+ }
6794
+ // TODO: [💝] Unite object for expecting amount and format
6795
+ if (expectations) {
6796
+ checkExpectations(expectations, processedResultString);
6797
+ }
6798
+ return {
6799
+ isValid: true,
6800
+ processedResultString,
6801
+ };
6802
+ }
6803
+ catch (error) {
6804
+ if (error instanceof ExpectError) {
6805
+ validationError = error;
6806
+ }
6807
+ else {
6808
+ // Re-throw non-ExpectError errors (like UnexpectedError)
6809
+ throw error;
6810
+ }
6811
+ return {
6812
+ isValid: false,
6813
+ processedResultString,
6814
+ error: validationError,
6815
+ };
6816
+ }
6193
6817
  }
6818
+
6194
6819
  /**
6195
6820
  * Executes one loop iteration, from joker resolution or task execution through validation.
6821
+ *
6822
+ * @private function of `executeAttempts`
6196
6823
  */
6197
6824
  async function executeSingleAttempt(options) {
6198
6825
  const { attempt, options: executeAttemptsOptions, llmTools, $ongoingTaskResult } = options;
@@ -6507,11 +7134,15 @@ function validateAttemptResult(options) {
6507
7134
  // Update the result string in case format processing modified it (e.g., JSON extraction)
6508
7135
  $ongoingTaskResult.$resultString = validationResult.processedResultString;
6509
7136
  }
7137
+
6510
7138
  /**
6511
- * Stores one failed attempt and reports the expectation error upstream.
7139
+ * Stores one failed attempt, reports the expectation error, and throws the aggregated retry error after the final
7140
+ * regular attempt.
7141
+ *
7142
+ * @private function of `executeAttempts`
6512
7143
  */
6513
- function recordFailedAttempt(options) {
6514
- const { error, attemptIndex, onProgress, $ongoingTaskResult } = options;
7144
+ function handleAttemptFailure(options) {
7145
+ const { error, attemptIndex, maxAttempts, maxExecutionAttempts, onProgress, pipelineIdentification, $ongoingTaskResult, } = options;
6515
7146
  $ongoingTaskResult.$expectError = error;
6516
7147
  $ongoingTaskResult.$failedResults.push({
6517
7148
  attemptIndex,
@@ -6521,39 +7152,7 @@ function recordFailedAttempt(options) {
6521
7152
  onProgress({
6522
7153
  errors: [error],
6523
7154
  });
6524
- }
6525
- /**
6526
- * Appends the prompt execution report for prompt-task attempts.
6527
- */
6528
- function reportPromptExecution(options) {
6529
- const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
6530
- if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
6531
- return;
6532
- }
6533
- // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
6534
- // In that case we don’t want to make a report about it because it’s not a llm execution error
6535
- const executionPromptReport = {
6536
- prompt: {
6537
- ...$ongoingTaskResult.$prompt,
6538
- // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
6539
- },
6540
- result: $ongoingTaskResult.$result || undefined,
6541
- error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
6542
- };
6543
- $executionReport.promptExecutions.push(executionPromptReport);
6544
- if (logLlmCall) {
6545
- logLlmCall({
6546
- modelName: 'model' /* <- TODO: How to get model name from the report */,
6547
- report: executionPromptReport,
6548
- });
6549
- }
6550
- }
6551
- /**
6552
- * Throws the aggregated retry error after the last regular attempt fails expectations.
6553
- */
6554
- function throwIfFinalAttemptFailed(options) {
6555
- const { attemptIndex, maxAttempts, maxExecutionAttempts, pipelineIdentification, $ongoingTaskResult } = options;
6556
- if ($ongoingTaskResult.$expectError === null || attemptIndex !== maxAttempts - 1) {
7155
+ if (attemptIndex !== maxAttempts - 1) {
6557
7156
  return;
6558
7157
  }
6559
7158
  throw new PipelineExecutionError(spaceTrim$1((block) => {
@@ -6598,6 +7197,136 @@ function quoteMultilineText(text) {
6598
7197
  .map((line) => `> ${line}`)
6599
7198
  .join('\n');
6600
7199
  }
7200
+
7201
+ /**
7202
+ * Appends the prompt execution report for prompt-task attempts.
7203
+ *
7204
+ * @private function of `executeAttempts`
7205
+ */
7206
+ function reportPromptExecution(options) {
7207
+ const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
7208
+ if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
7209
+ return;
7210
+ }
7211
+ // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
7212
+ // In that case we don’t want to make a report about it because it’s not a llm execution error
7213
+ const executionPromptReport = {
7214
+ prompt: {
7215
+ ...$ongoingTaskResult.$prompt,
7216
+ // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
7217
+ },
7218
+ result: $ongoingTaskResult.$result || undefined,
7219
+ error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
7220
+ };
7221
+ $executionReport.promptExecutions.push(executionPromptReport);
7222
+ if (logLlmCall) {
7223
+ logLlmCall({
7224
+ modelName: 'model' /* <- TODO: How to get model name from the report */,
7225
+ report: executionPromptReport,
7226
+ });
7227
+ }
7228
+ }
7229
+
7230
+ /**
7231
+ * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
7232
+ * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
7233
+ * Throws errors if execution fails after all attempts.
7234
+ *
7235
+ * @param options - The options for execution, including task, parameters, pipeline, and configuration.
7236
+ * @returns The result string of the executed task.
7237
+ *
7238
+ * @private internal utility of `createPipelineExecutor`
7239
+ */
7240
+ async function executeAttempts(options) {
7241
+ const $ongoingTaskResult = createOngoingTaskResult();
7242
+ const llmTools = getSingleLlmExecutionTools(options.tools.llm);
7243
+ attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
7244
+ const attempt = createAttemptDescriptor({
7245
+ attemptIndex,
7246
+ jokerParameterNames: options.jokerParameterNames,
7247
+ pipelineIdentification: options.pipelineIdentification,
7248
+ });
7249
+ resetAttemptExecutionState($ongoingTaskResult);
7250
+ try {
7251
+ await executeSingleAttempt({
7252
+ attempt,
7253
+ options,
7254
+ llmTools,
7255
+ $ongoingTaskResult,
7256
+ });
7257
+ break attempts;
7258
+ }
7259
+ catch (error) {
7260
+ if (!(error instanceof ExpectError)) {
7261
+ throw error;
7262
+ }
7263
+ handleAttemptFailure({
7264
+ error,
7265
+ attemptIndex,
7266
+ maxAttempts: options.maxAttempts,
7267
+ maxExecutionAttempts: options.maxExecutionAttempts,
7268
+ onProgress: options.onProgress,
7269
+ pipelineIdentification: options.pipelineIdentification,
7270
+ $ongoingTaskResult,
7271
+ });
7272
+ }
7273
+ finally {
7274
+ reportPromptExecution({
7275
+ attempt,
7276
+ task: options.task,
7277
+ $executionReport: options.$executionReport,
7278
+ logLlmCall: options.logLlmCall,
7279
+ $ongoingTaskResult,
7280
+ });
7281
+ }
7282
+ }
7283
+ return getSuccessfulResultString({
7284
+ pipelineIdentification: options.pipelineIdentification,
7285
+ $ongoingTaskResult,
7286
+ });
7287
+ }
7288
+ /**
7289
+ * Creates mutable attempt state for one task execution lifecycle.
7290
+ */
7291
+ function createOngoingTaskResult() {
7292
+ return {
7293
+ $result: null,
7294
+ $resultString: null,
7295
+ $expectError: null,
7296
+ $scriptPipelineExecutionErrors: [],
7297
+ $failedResults: [],
7298
+ };
7299
+ }
7300
+ /**
7301
+ * Resolves the bookkeeping for one loop iteration, including joker lookup.
7302
+ */
7303
+ function createAttemptDescriptor(options) {
7304
+ const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
7305
+ const isJokerAttempt = attemptIndex < 0;
7306
+ const jokerParameterName = isJokerAttempt
7307
+ ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
7308
+ : undefined;
7309
+ if (isJokerAttempt && !jokerParameterName) {
7310
+ throw new UnexpectedError(spaceTrim$1((block) => `
7311
+ Joker not found in attempt ${attemptIndex}
7312
+
7313
+ ${block(pipelineIdentification)}
7314
+ `));
7315
+ }
7316
+ return {
7317
+ attemptIndex,
7318
+ isJokerAttempt,
7319
+ jokerParameterName,
7320
+ };
7321
+ }
7322
+ /**
7323
+ * Clears the per-attempt result slots while preserving cumulative failure history.
7324
+ */
7325
+ function resetAttemptExecutionState($ongoingTaskResult) {
7326
+ $ongoingTaskResult.$result = null;
7327
+ $ongoingTaskResult.$resultString = null;
7328
+ $ongoingTaskResult.$expectError = null;
7329
+ }
6601
7330
  /**
6602
7331
  * Returns the successful result string or raises an unexpected internal-state error.
6603
7332
  */