@promptbook/pdf 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 +1781 -1052
  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 +1781 -1052
  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
@@ -24,7 +24,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
24
24
  * @generated
25
25
  * @see https://github.com/webgptorg/promptbook
26
26
  */
27
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
27
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
28
28
  /**
29
29
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
30
30
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -306,6 +306,111 @@ function checkChannelValue(channelName, value) {
306
306
  }
307
307
  }
308
308
 
309
+ /**
310
+ * Shared immutable channel storage and serialization helpers for `Color`.
311
+ *
312
+ * @private base class of Color
313
+ */
314
+ class ColorValue {
315
+ constructor(red, green, blue, alpha = 255) {
316
+ this.red = red;
317
+ this.green = green;
318
+ this.blue = blue;
319
+ this.alpha = alpha;
320
+ checkChannelValue('Red', red);
321
+ checkChannelValue('Green', green);
322
+ checkChannelValue('Blue', blue);
323
+ checkChannelValue('Alpha', alpha);
324
+ }
325
+ /**
326
+ * Shortcut for `red` property
327
+ * Number from 0 to 255
328
+ * @alias red
329
+ */
330
+ get r() {
331
+ return this.red;
332
+ }
333
+ /**
334
+ * Shortcut for `green` property
335
+ * Number from 0 to 255
336
+ * @alias green
337
+ */
338
+ get g() {
339
+ return this.green;
340
+ }
341
+ /**
342
+ * Shortcut for `blue` property
343
+ * Number from 0 to 255
344
+ * @alias blue
345
+ */
346
+ get b() {
347
+ return this.blue;
348
+ }
349
+ /**
350
+ * Shortcut for `alpha` property
351
+ * Number from 0 (transparent) to 255 (opaque)
352
+ * @alias alpha
353
+ */
354
+ get a() {
355
+ return this.alpha;
356
+ }
357
+ /**
358
+ * Shortcut for `alpha` property
359
+ * Number from 0 (transparent) to 255 (opaque)
360
+ * @alias alpha
361
+ */
362
+ get opacity() {
363
+ return this.alpha;
364
+ }
365
+ /**
366
+ * Shortcut for 1-`alpha` property
367
+ */
368
+ get transparency() {
369
+ return 255 - this.alpha;
370
+ }
371
+ clone() {
372
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
373
+ }
374
+ toString() {
375
+ return this.toHex();
376
+ }
377
+ toHex() {
378
+ if (this.alpha === 255) {
379
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
380
+ .toString(16)
381
+ .padStart(2, '0')}`;
382
+ }
383
+ else {
384
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
385
+ .toString(16)
386
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
387
+ }
388
+ }
389
+ toRgb() {
390
+ if (this.alpha === 255) {
391
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
392
+ }
393
+ else {
394
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
395
+ }
396
+ }
397
+ toHsl() {
398
+ throw new Error(`Getting HSL is not implemented`);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Checks if the given value is a valid hex color string
404
+ *
405
+ * @param value - value to check
406
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
407
+ *
408
+ * @private function of Color
409
+ */
410
+ function isHexColorString(value) {
411
+ 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));
412
+ }
413
+
309
414
  /**
310
415
  * Constant for short hex lengths.
311
416
  */
@@ -517,16 +622,53 @@ function parseAlphaValue(value) {
517
622
 
518
623
  /**
519
624
  * Pattern matching hsl regex.
625
+ *
626
+ * @private function of Color
520
627
  */
521
628
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
522
629
  /**
523
630
  * Pattern matching RGB regex.
631
+ *
632
+ * @private function of Color
524
633
  */
525
634
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
526
635
  /**
527
636
  * Pattern matching rgba regex.
637
+ *
638
+ * @private function of Color
528
639
  */
529
640
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
641
+ /**
642
+ * Parses a supported color string into RGBA channels.
643
+ *
644
+ * @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`,...
645
+ * @returns RGBA channel values.
646
+ *
647
+ * @private function of Color
648
+ */
649
+ function parseColorString(color) {
650
+ const trimmed = color.trim();
651
+ const cssColor = CSS_COLORS[trimmed];
652
+ if (cssColor) {
653
+ return parseColorString(cssColor);
654
+ }
655
+ else if (isHexColorString(trimmed)) {
656
+ return parseHexColor(trimmed);
657
+ }
658
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
659
+ return parseHslColor(trimmed);
660
+ }
661
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
662
+ return parseRgbColor(trimmed);
663
+ }
664
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
665
+ return parseRgbaColor(trimmed);
666
+ }
667
+ else {
668
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
669
+ }
670
+ }
671
+
530
672
  /**
531
673
  * Color object represents an RGB color with alpha channel
532
674
  *
@@ -534,7 +676,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
534
676
  *
535
677
  * @public exported from `@promptbook/color`
536
678
  */
537
- class Color {
679
+ class Color extends ColorValue {
538
680
  /**
539
681
  * Creates a new Color instance from miscellaneous formats
540
682
  * - It can receive Color instance and just return the same instance
@@ -607,25 +749,7 @@ class Color {
607
749
  * @returns Color object
608
750
  */
609
751
  static fromString(color) {
610
- const trimmed = color.trim();
611
- if (CSS_COLORS[trimmed]) {
612
- return Color.fromString(CSS_COLORS[trimmed]);
613
- }
614
- else if (Color.isHexColorString(trimmed)) {
615
- return Color.fromHex(trimmed);
616
- }
617
- if (HSL_REGEX_PATTERN.test(trimmed)) {
618
- return Color.fromHsl(trimmed);
619
- }
620
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
621
- return Color.fromRgbString(trimmed);
622
- }
623
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
624
- return Color.fromRgbaString(trimmed);
625
- }
626
- else {
627
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
628
- }
752
+ return Color.fromColorChannels(parseColorString(color));
629
753
  }
630
754
  /**
631
755
  * Gets common color
@@ -655,8 +779,7 @@ class Color {
655
779
  * @returns Color object
656
780
  */
657
781
  static fromHex(hex) {
658
- const { red, green, blue, alpha } = parseHexColor(hex);
659
- return take(new Color(red, green, blue, alpha));
782
+ return Color.fromColorChannels(parseHexColor(hex));
660
783
  }
661
784
  /**
662
785
  * Creates a new Color instance from color in hsl format
@@ -665,8 +788,7 @@ class Color {
665
788
  * @returns Color object
666
789
  */
667
790
  static fromHsl(hsl) {
668
- const { red, green, blue, alpha } = parseHslColor(hsl);
669
- return take(new Color(red, green, blue, alpha));
791
+ return Color.fromColorChannels(parseHslColor(hsl));
670
792
  }
671
793
  /**
672
794
  * Creates a new Color instance from color in rgb format
@@ -675,8 +797,7 @@ class Color {
675
797
  * @returns Color object
676
798
  */
677
799
  static fromRgbString(rgb) {
678
- const { red, green, blue, alpha } = parseRgbColor(rgb);
679
- return take(new Color(red, green, blue, alpha));
800
+ return Color.fromColorChannels(parseRgbColor(rgb));
680
801
  }
681
802
  /**
682
803
  * Creates a new Color instance from color in rbga format
@@ -685,8 +806,7 @@ class Color {
685
806
  * @returns Color object
686
807
  */
687
808
  static fromRgbaString(rgba) {
688
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
689
- return take(new Color(red, green, blue, alpha));
809
+ return Color.fromColorChannels(parseRgbaColor(rgba));
690
810
  }
691
811
  /**
692
812
  * Creates a new Color for color channels values
@@ -698,7 +818,7 @@ class Color {
698
818
  * @returns Color object
699
819
  */
700
820
  static fromValues(red, green, blue, alpha = 255) {
701
- return take(new Color(red, green, blue, alpha));
821
+ return Color.fromColorChannels({ red, green, blue, alpha });
702
822
  }
703
823
  /**
704
824
  * Checks if the given value is a valid Color object.
@@ -731,8 +851,7 @@ class Color {
731
851
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
732
852
  */
733
853
  static isHexColorString(value) {
734
- return (typeof value === 'string' &&
735
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
854
+ return isHexColorString(value);
736
855
  }
737
856
  /**
738
857
  * Creates new Color object
@@ -745,89 +864,13 @@ class Color {
745
864
  * @param alpha number from 0 (transparent) to 255 (opaque)
746
865
  */
747
866
  constructor(red, green, blue, alpha = 255) {
748
- this.red = red;
749
- this.green = green;
750
- this.blue = blue;
751
- this.alpha = alpha;
752
- checkChannelValue('Red', red);
753
- checkChannelValue('Green', green);
754
- checkChannelValue('Blue', blue);
755
- checkChannelValue('Alpha', alpha);
756
- }
757
- /**
758
- * Shortcut for `red` property
759
- * Number from 0 to 255
760
- * @alias red
761
- */
762
- get r() {
763
- return this.red;
764
- }
765
- /**
766
- * Shortcut for `green` property
767
- * Number from 0 to 255
768
- * @alias green
769
- */
770
- get g() {
771
- return this.green;
772
- }
773
- /**
774
- * Shortcut for `blue` property
775
- * Number from 0 to 255
776
- * @alias blue
777
- */
778
- get b() {
779
- return this.blue;
780
- }
781
- /**
782
- * Shortcut for `alpha` property
783
- * Number from 0 (transparent) to 255 (opaque)
784
- * @alias alpha
785
- */
786
- get a() {
787
- return this.alpha;
788
- }
789
- /**
790
- * Shortcut for `alpha` property
791
- * Number from 0 (transparent) to 255 (opaque)
792
- * @alias alpha
793
- */
794
- get opacity() {
795
- return this.alpha;
796
- }
797
- /**
798
- * Shortcut for 1-`alpha` property
799
- */
800
- get transparency() {
801
- return 255 - this.alpha;
802
- }
803
- clone() {
804
- return take(new Color(this.red, this.green, this.blue, this.alpha));
805
- }
806
- toString() {
807
- return this.toHex();
808
- }
809
- toHex() {
810
- if (this.alpha === 255) {
811
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
812
- .toString(16)
813
- .padStart(2, '0')}`;
814
- }
815
- else {
816
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
817
- .toString(16)
818
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
819
- }
867
+ super(red, green, blue, alpha);
820
868
  }
821
- toRgb() {
822
- if (this.alpha === 255) {
823
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
824
- }
825
- else {
826
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
827
- }
869
+ createColor(red, green, blue, alpha) {
870
+ return new Color(red, green, blue, alpha);
828
871
  }
829
- toHsl() {
830
- throw new Error(`Getting HSL is not implemented`);
872
+ static fromColorChannels({ red, green, blue, alpha }) {
873
+ return take(new Color(red, green, blue, alpha));
831
874
  }
832
875
  }
833
876
 
@@ -2046,6 +2089,60 @@ function validatePipelineString(pipelineString) {
2046
2089
  }
2047
2090
  // TODO: [🧠][🈴] Where is the best location for this file
2048
2091
 
2092
+ /**
2093
+ * Appends one markdown block to an existing markdown document.
2094
+ *
2095
+ * @private internal utility of `pipelineJsonToString`
2096
+ */
2097
+ function appendMarkdownBlock(pipelineString, markdownBlock) {
2098
+ return spaceTrim$1((block) => `
2099
+ ${block(pipelineString)}
2100
+
2101
+ ${block(markdownBlock)}
2102
+ `);
2103
+ }
2104
+
2105
+ /**
2106
+ * Collects pipeline-level commands in the existing serialization order.
2107
+ *
2108
+ * @private internal utility of `pipelineJsonToString`
2109
+ */
2110
+ function createPipelineCommands(pipelineJson) {
2111
+ const { pipelineUrl, bookVersion, parameters } = pipelineJson;
2112
+ const commands = [];
2113
+ if (pipelineUrl) {
2114
+ commands.push(`PIPELINE URL ${pipelineUrl}`);
2115
+ }
2116
+ if (bookVersion !== `undefined`) {
2117
+ commands.push(`BOOK VERSION ${bookVersion}`);
2118
+ }
2119
+ commands.push(...createParameterCommands(parameters, 'INPUT PARAMETER', ({ isInput }) => isInput));
2120
+ commands.push(...createParameterCommands(parameters, 'OUTPUT PARAMETER', ({ isOutput }) => isOutput));
2121
+ return commands;
2122
+ }
2123
+ /**
2124
+ * Builds one group of parameter commands while preserving the original parameter order.
2125
+ *
2126
+ * @private internal utility of `createPipelineCommands`
2127
+ */
2128
+ function createParameterCommands(parameters, commandPrefix, isIncluded) {
2129
+ return parameters
2130
+ .filter((parameter) => isIncluded(parameter))
2131
+ .map((parameter) => `${commandPrefix} ${parameterJsonToString(parameter)}`);
2132
+ }
2133
+ /**
2134
+ * Converts one parameter JSON declaration to the serialized inline form.
2135
+ *
2136
+ * @private internal utility of `createPipelineCommands`
2137
+ */
2138
+ function parameterJsonToString(parameterJson) {
2139
+ const { name, description } = parameterJson;
2140
+ if (!description) {
2141
+ return `{${name}}`;
2142
+ }
2143
+ return `{${name}} ${description}`;
2144
+ }
2145
+
2049
2146
  /**
2050
2147
  * Prettify the html code
2051
2148
  *
@@ -2060,152 +2157,222 @@ function prettifyMarkdown(content) {
2060
2157
  }
2061
2158
 
2062
2159
  /**
2063
- * Makes first letter of a string uppercase
2064
- *
2065
- * Note: [🔂] This function is idempotent.
2160
+ * Creates the initial markdown heading and description of a pipeline.
2066
2161
  *
2067
- * @public exported from `@promptbook/utils`
2162
+ * @private internal utility of `pipelineJsonToString`
2068
2163
  */
2069
- function capitalize(word) {
2070
- return word.substring(0, 1).toUpperCase() + word.substring(1);
2164
+ function createPipelineIntroduction(pipelineJson) {
2165
+ const { title, description } = pipelineJson;
2166
+ const pipelineIntroduction = spaceTrim$1((block) => `
2167
+ # ${title}
2168
+
2169
+ ${block(description || '')}
2170
+ `);
2171
+ // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
2172
+ return prettifyMarkdown(pipelineIntroduction);
2071
2173
  }
2072
2174
 
2073
2175
  /**
2074
- * Converts promptbook in JSON format to string format
2176
+ * Renders commands as markdown bullet items.
2075
2177
  *
2076
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
2077
- * @param pipelineJson Promptbook in JSON format (.bookc)
2078
- * @returns Promptbook in string format (.book.md)
2178
+ * @private internal utility of `pipelineJsonToString`
2179
+ */
2180
+ function stringifyCommands(commands) {
2181
+ return commands.map((command) => `- ${command}`).join('\n');
2182
+ }
2183
+
2184
+ /**
2185
+ * Makes first letter of a string uppercase
2079
2186
  *
2080
- * @public exported from `@promptbook/core`
2187
+ * Note: [🔂] This function is idempotent.
2188
+ *
2189
+ * @public exported from `@promptbook/utils`
2081
2190
  */
2082
- function pipelineJsonToString(pipelineJson) {
2083
- const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
2084
- let pipelineString = spaceTrim$1((block) => `
2085
- # ${title}
2191
+ function capitalize(word) {
2192
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
2193
+ }
2086
2194
 
2087
- ${block(description || '')}
2088
- `);
2195
+ /**
2196
+ * Collects all task-specific serialization details.
2197
+ *
2198
+ * @private internal utility of `pipelineJsonToString`
2199
+ */
2200
+ function createTaskSerialization(task) {
2201
+ const taskTypeSerialization = createTaskTypeSerialization(task);
2202
+ return {
2203
+ commands: [
2204
+ ...taskTypeSerialization.commands,
2205
+ ...createJokerCommands(task),
2206
+ ...createPostprocessingCommands(task),
2207
+ ...createExpectationCommands(task),
2208
+ ...createFormatCommands(task),
2209
+ ],
2210
+ contentLanguage: taskTypeSerialization.contentLanguage,
2211
+ };
2212
+ }
2213
+ /**
2214
+ * Collects commands and content language driven by the task type.
2215
+ *
2216
+ * @private internal utility of `createTaskSerialization`
2217
+ */
2218
+ function createTaskTypeSerialization(task) {
2219
+ if (task.taskType === 'PROMPT_TASK') {
2220
+ return {
2221
+ commands: createPromptTaskCommands(task),
2222
+ contentLanguage: 'text',
2223
+ };
2224
+ }
2225
+ if (task.taskType === 'SIMPLE_TASK') {
2226
+ return {
2227
+ commands: ['SIMPLE TEMPLATE'],
2228
+ contentLanguage: 'text',
2229
+ };
2230
+ }
2231
+ if (task.taskType === 'SCRIPT_TASK') {
2232
+ return {
2233
+ commands: ['SCRIPT'],
2234
+ contentLanguage: task.contentLanguage || '',
2235
+ };
2236
+ }
2237
+ if (task.taskType === 'DIALOG_TASK') {
2238
+ return {
2239
+ commands: ['DIALOG'],
2240
+ contentLanguage: 'text',
2241
+ };
2242
+ }
2243
+ return {
2244
+ commands: [],
2245
+ contentLanguage: 'text',
2246
+ };
2247
+ }
2248
+ /**
2249
+ * Collects prompt-task-specific commands.
2250
+ *
2251
+ * @private internal utility of `createTaskSerialization`
2252
+ */
2253
+ function createPromptTaskCommands(task) {
2254
+ const { modelName, modelVariant } = task.modelRequirements || {};
2089
2255
  const commands = [];
2090
- if (pipelineUrl) {
2091
- commands.push(`PIPELINE URL ${pipelineUrl}`);
2256
+ // Note: Do nothing, it is default
2257
+ // commands.push(`PROMPT`);
2258
+ if (modelVariant) {
2259
+ commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
2092
2260
  }
2093
- if (bookVersion !== `undefined`) {
2094
- commands.push(`BOOK VERSION ${bookVersion}`);
2261
+ if (modelName) {
2262
+ commands.push(`MODEL NAME \`${modelName}\``);
2095
2263
  }
2096
- // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
2097
- pipelineString = prettifyMarkdown(pipelineString);
2098
- for (const parameter of parameters.filter(({ isInput }) => isInput)) {
2099
- commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
2264
+ return commands;
2265
+ }
2266
+ /**
2267
+ * Collects joker commands.
2268
+ *
2269
+ * @private internal utility of `createTaskSerialization`
2270
+ */
2271
+ function createJokerCommands(task) {
2272
+ var _a;
2273
+ return ((_a = task.jokerParameterNames) === null || _a === void 0 ? void 0 : _a.map((joker) => `JOKER {${joker}}`)) || [];
2274
+ }
2275
+ /**
2276
+ * Collects postprocessing commands.
2277
+ *
2278
+ * @private internal utility of `createTaskSerialization`
2279
+ */
2280
+ function createPostprocessingCommands(task) {
2281
+ var _a;
2282
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
2283
+ }
2284
+ /**
2285
+ * Collects expectation commands.
2286
+ *
2287
+ * @private internal utility of `createTaskSerialization`
2288
+ */
2289
+ function createExpectationCommands(task) {
2290
+ if (!task.expectations) {
2291
+ return [];
2100
2292
  }
2101
- for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
2102
- commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
2293
+ return Object.entries(task.expectations).flatMap(([unit, expectation]) => createExpectationCommandsForUnit(unit, expectation.min, expectation.max));
2294
+ }
2295
+ /**
2296
+ * Collects expectation commands for a single unit.
2297
+ *
2298
+ * @private internal utility of `createTaskSerialization`
2299
+ */
2300
+ function createExpectationCommandsForUnit(unit, min, max) {
2301
+ if (min === max) {
2302
+ return [`EXPECT EXACTLY ${min} ${formatExpectationUnit(unit, min)}`];
2103
2303
  }
2104
- pipelineString = spaceTrim$1((block) => `
2105
- ${block(pipelineString)}
2106
-
2107
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
2108
- `);
2109
- for (const task of tasks) {
2110
- const {
2111
- /* Note: Not using:> name, */
2112
- title, description,
2113
- /* Note: dependentParameterNames, */
2114
- jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
2115
- const commands = [];
2116
- let contentLanguage = 'text';
2117
- if (taskType === 'PROMPT_TASK') {
2118
- const { modelRequirements } = task;
2119
- const { modelName, modelVariant } = modelRequirements || {};
2120
- // Note: Do nothing, it is default
2121
- // commands.push(`PROMPT`);
2122
- if (modelVariant) {
2123
- commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
2124
- }
2125
- if (modelName) {
2126
- commands.push(`MODEL NAME \`${modelName}\``);
2127
- }
2128
- }
2129
- else if (taskType === 'SIMPLE_TASK') {
2130
- commands.push(`SIMPLE TEMPLATE`);
2131
- // Note: Nothing special here
2132
- }
2133
- else if (taskType === 'SCRIPT_TASK') {
2134
- commands.push(`SCRIPT`);
2135
- if (task.contentLanguage) {
2136
- contentLanguage = task.contentLanguage;
2137
- }
2138
- else {
2139
- contentLanguage = '';
2140
- }
2141
- }
2142
- else if (taskType === 'DIALOG_TASK') {
2143
- commands.push(`DIALOG`);
2144
- // Note: Nothing special here
2145
- } // <- }else if([🅱]
2146
- if (jokers) {
2147
- for (const joker of jokers) {
2148
- commands.push(`JOKER {${joker}}`);
2149
- }
2150
- } /* not else */
2151
- if (postprocessing) {
2152
- for (const postprocessingFunctionName of postprocessing) {
2153
- commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
2154
- }
2155
- } /* not else */
2156
- if (expectations) {
2157
- for (const [unit, { min, max }] of Object.entries(expectations)) {
2158
- if (min === max) {
2159
- commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
2160
- }
2161
- else {
2162
- if (min !== undefined) {
2163
- commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
2164
- } /* not else */
2165
- if (max !== undefined) {
2166
- commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
2167
- }
2168
- }
2169
- }
2170
- } /* not else */
2171
- if (format) {
2172
- if (format === 'JSON') {
2173
- // TODO: @deprecated remove
2174
- commands.push(`FORMAT JSON`);
2175
- }
2176
- } /* not else */
2177
- pipelineString = spaceTrim$1((block) => `
2178
- ${block(pipelineString)}
2304
+ const commands = [];
2305
+ if (min !== undefined) {
2306
+ commands.push(`EXPECT MIN ${min} ${formatExpectationUnit(unit, min)}`);
2307
+ }
2308
+ if (max !== undefined) {
2309
+ commands.push(`EXPECT MAX ${max} ${formatExpectationUnit(unit, max)}`);
2310
+ }
2311
+ return commands;
2312
+ }
2313
+ /**
2314
+ * Formats the expectation unit exactly as the legacy serializer does.
2315
+ *
2316
+ * @private internal utility of `createTaskSerialization`
2317
+ */
2318
+ function formatExpectationUnit(unit, amount) {
2319
+ return capitalize(unit + (amount > 1 ? 's' : ''));
2320
+ }
2321
+ /**
2322
+ * Collects format commands.
2323
+ *
2324
+ * @private internal utility of `createTaskSerialization`
2325
+ */
2326
+ function createFormatCommands(task) {
2327
+ if (task.format === 'JSON') {
2328
+ // TODO: @deprecated remove
2329
+ return ['FORMAT JSON'];
2330
+ }
2331
+ return [];
2332
+ }
2179
2333
 
2180
- ## ${title}
2334
+ /**
2335
+ * Stringifies one task section of the pipeline.
2336
+ *
2337
+ * @private internal utility of `pipelineJsonToString`
2338
+ */
2339
+ function stringifyTask(task) {
2340
+ const { title, description, content, resultingParameterName } = task;
2341
+ const { commands, contentLanguage } = createTaskSerialization(task);
2342
+ return spaceTrim$1((block) => `
2343
+ ## ${title}
2181
2344
 
2182
- ${block(description || '')}
2345
+ ${block(description || '')}
2183
2346
 
2184
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
2347
+ ${block(stringifyCommands(commands))}
2185
2348
 
2186
- \`\`\`${contentLanguage}
2187
- ${block(spaceTrim$1(content))}
2188
- \`\`\`
2349
+ \`\`\`${contentLanguage}
2350
+ ${block(spaceTrim$1(content))}
2351
+ \`\`\`
2189
2352
 
2190
- \`-> {${resultingParameterName}}\`
2191
- `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
2192
- // <- TODO: [main] !!3 Escape
2193
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
2194
- }
2195
- return validatePipelineString(pipelineString);
2353
+ \`-> {${resultingParameterName}}\`
2354
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
2355
+ // <- TODO: [main] !!3 Escape
2356
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
2196
2357
  }
2358
+
2197
2359
  /**
2198
- * Handles task parameter Json to string.
2360
+ * Converts promptbook in JSON format to string format
2199
2361
  *
2200
- * @private internal utility of `pipelineJsonToString`
2362
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
2363
+ * @param pipelineJson Promptbook in JSON format (.bookc)
2364
+ * @returns Promptbook in string format (.book.md)
2365
+ *
2366
+ * @public exported from `@promptbook/core`
2201
2367
  */
2202
- function taskParameterJsonToString(taskParameterJson) {
2203
- const { name, description } = taskParameterJson;
2204
- let parameterString = `{${name}}`;
2205
- if (description) {
2206
- parameterString = `${parameterString} ${description}`;
2368
+ function pipelineJsonToString(pipelineJson) {
2369
+ let pipelineString = createPipelineIntroduction(pipelineJson);
2370
+ const pipelineCommands = createPipelineCommands(pipelineJson);
2371
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
2372
+ for (const task of pipelineJson.tasks) {
2373
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
2207
2374
  }
2208
- return parameterString;
2375
+ return validatePipelineString(pipelineString);
2209
2376
  }
2210
2377
  // TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
2211
2378
  // TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
@@ -2277,120 +2444,183 @@ function $deepFreeze(objectValue) {
2277
2444
  * @public exported from `@promptbook/utils`
2278
2445
  */
2279
2446
  function checkSerializableAsJson(options) {
2280
- const { value, name, message } = options;
2447
+ checkSerializableValue(options);
2448
+ }
2449
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2450
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2451
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2452
+ /**
2453
+ * Checks one value and dispatches to the appropriate specialized validator.
2454
+ *
2455
+ * @private function of `checkSerializableAsJson`
2456
+ */
2457
+ function checkSerializableValue(options) {
2458
+ const { value } = options;
2459
+ if (isSerializablePrimitive(value)) {
2460
+ return;
2461
+ }
2281
2462
  if (value === undefined) {
2282
- throw new UnexpectedError(`${name} is undefined`);
2463
+ throw new UnexpectedError(`${options.name} is undefined`);
2283
2464
  }
2284
- else if (value === null) {
2285
- return;
2465
+ if (typeof value === 'symbol') {
2466
+ throw new UnexpectedError(`${options.name} is symbol`);
2286
2467
  }
2287
- else if (typeof value === 'boolean') {
2288
- return;
2468
+ if (typeof value === 'function') {
2469
+ throw new UnexpectedError(`${options.name} is function`);
2289
2470
  }
2290
- else if (typeof value === 'number' && !isNaN(value)) {
2471
+ if (Array.isArray(value)) {
2472
+ checkSerializableArray(options, value);
2291
2473
  return;
2292
2474
  }
2293
- else if (typeof value === 'string') {
2475
+ if (value !== null && typeof value === 'object') {
2476
+ checkSerializableObject(options, value);
2294
2477
  return;
2295
2478
  }
2296
- else if (typeof value === 'symbol') {
2297
- throw new UnexpectedError(`${name} is symbol`);
2298
- }
2299
- else if (typeof value === 'function') {
2300
- throw new UnexpectedError(`${name} is function`);
2301
- }
2302
- else if (typeof value === 'object' && Array.isArray(value)) {
2303
- for (let i = 0; i < value.length; i++) {
2304
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
2305
- }
2479
+ throwUnknownTypeError(options);
2480
+ }
2481
+ /**
2482
+ * Checks the primitive values that are directly JSON serializable.
2483
+ *
2484
+ * @private function of `checkSerializableAsJson`
2485
+ */
2486
+ function isSerializablePrimitive(value) {
2487
+ return (value === null ||
2488
+ typeof value === 'boolean' ||
2489
+ (typeof value === 'number' && !isNaN(value)) ||
2490
+ typeof value === 'string');
2491
+ }
2492
+ /**
2493
+ * Recursively checks JSON array items.
2494
+ *
2495
+ * @private function of `checkSerializableAsJson`
2496
+ */
2497
+ function checkSerializableArray(context, arrayValue) {
2498
+ for (let index = 0; index < arrayValue.length; index++) {
2499
+ checkSerializableAsJson({
2500
+ ...context,
2501
+ name: `${context.name}[${index}]`,
2502
+ value: arrayValue[index],
2503
+ });
2306
2504
  }
2307
- else if (typeof value === 'object') {
2308
- if (value instanceof Date) {
2309
- throw new UnexpectedError(spaceTrim$1((block) => `
2310
- \`${name}\` is Date
2505
+ }
2506
+ /**
2507
+ * Checks object-like values and dispatches special unsupported built-ins.
2508
+ *
2509
+ * @private function of `checkSerializableAsJson`
2510
+ */
2511
+ function checkSerializableObject(context, objectValue) {
2512
+ checkUnsupportedObjectType(context, objectValue);
2513
+ checkSerializableObjectEntries(context, objectValue);
2514
+ assertJsonStringificationSucceeds(context, objectValue);
2515
+ }
2516
+ /**
2517
+ * Rejects built-in objects that must be converted before JSON serialization.
2518
+ *
2519
+ * @private function of `checkSerializableAsJson`
2520
+ */
2521
+ function checkUnsupportedObjectType(context, objectValue) {
2522
+ if (objectValue instanceof Date) {
2523
+ throw new UnexpectedError(spaceTrim$1((block) => `
2524
+ \`${context.name}\` is Date
2311
2525
 
2312
- Use \`string_date_iso8601\` instead
2526
+ Use \`string_date_iso8601\` instead
2313
2527
 
2314
- Additional message for \`${name}\`:
2315
- ${block(message || '(nothing)')}
2316
- `));
2317
- }
2318
- else if (value instanceof Map) {
2319
- throw new UnexpectedError(`${name} is Map`);
2320
- }
2321
- else if (value instanceof Set) {
2322
- throw new UnexpectedError(`${name} is Set`);
2323
- }
2324
- else if (value instanceof RegExp) {
2325
- throw new UnexpectedError(`${name} is RegExp`);
2326
- }
2327
- else if (value instanceof Error) {
2328
- throw new UnexpectedError(spaceTrim$1((block) => `
2329
- \`${name}\` is unserialized Error
2528
+ Additional message for \`${context.name}\`:
2529
+ ${block(context.message || '(nothing)')}
2530
+ `));
2531
+ }
2532
+ if (objectValue instanceof Map) {
2533
+ throw new UnexpectedError(`${context.name} is Map`);
2534
+ }
2535
+ if (objectValue instanceof Set) {
2536
+ throw new UnexpectedError(`${context.name} is Set`);
2537
+ }
2538
+ if (objectValue instanceof RegExp) {
2539
+ throw new UnexpectedError(`${context.name} is RegExp`);
2540
+ }
2541
+ if (objectValue instanceof Error) {
2542
+ throw new UnexpectedError(spaceTrim$1((block) => `
2543
+ \`${context.name}\` is unserialized Error
2330
2544
 
2331
- Use function \`serializeError\`
2545
+ Use function \`serializeError\`
2332
2546
 
2333
- Additional message for \`${name}\`:
2334
- ${block(message || '(nothing)')}
2547
+ Additional message for \`${context.name}\`:
2548
+ ${block(context.message || '(nothing)')}
2335
2549
 
2336
- `));
2550
+ `));
2551
+ }
2552
+ }
2553
+ /**
2554
+ * Recursively checks object properties while preserving omitted `undefined` keys.
2555
+ *
2556
+ * @private function of `checkSerializableAsJson`
2557
+ */
2558
+ function checkSerializableObjectEntries(context, objectValue) {
2559
+ for (const [subName, subValue] of Object.entries(objectValue)) {
2560
+ if (subValue === undefined) {
2561
+ // Note: undefined in object is serializable - it is just omitted
2562
+ continue;
2337
2563
  }
2338
- else {
2339
- for (const [subName, subValue] of Object.entries(value)) {
2340
- if (subValue === undefined) {
2341
- // Note: undefined in object is serializable - it is just omitted
2342
- continue;
2343
- }
2344
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
2345
- }
2346
- try {
2347
- JSON.stringify(value); // <- TODO: [0]
2348
- }
2349
- catch (error) {
2350
- assertsError(error);
2351
- throw new UnexpectedError(spaceTrim$1((block) => `
2352
- \`${name}\` is not serializable
2564
+ checkSerializableAsJson({
2565
+ ...context,
2566
+ name: `${context.name}.${subName}`,
2567
+ value: subValue,
2568
+ });
2569
+ }
2570
+ }
2571
+ /**
2572
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
2573
+ *
2574
+ * @private function of `checkSerializableAsJson`
2575
+ */
2576
+ function assertJsonStringificationSucceeds(context, objectValue) {
2577
+ try {
2578
+ JSON.stringify(objectValue); // <- TODO: [0]
2579
+ }
2580
+ catch (error) {
2581
+ assertsError(error);
2582
+ throw new UnexpectedError(spaceTrim$1((block) => `
2583
+ \`${context.name}\` is not serializable
2353
2584
 
2354
- ${block(error.stack || error.message)}
2585
+ ${block(error.stack || error.message)}
2355
2586
 
2356
- Additional message for \`${name}\`:
2357
- ${block(message || '(nothing)')}
2358
- `));
2587
+ Additional message for \`${context.name}\`:
2588
+ ${block(context.message || '(nothing)')}
2589
+ `));
2590
+ }
2591
+ /*
2592
+ TODO: [0] Is there some more elegant way to check circular references?
2593
+ const seen = new Set();
2594
+ const stack = [{ value }];
2595
+ while (stack.length > 0) {
2596
+ const { value } = stack.pop()!;
2597
+ if (typeof value === 'object' && value !== null) {
2598
+ if (seen.has(value)) {
2599
+ throw new UnexpectedError(`${name} has circular reference`);
2359
2600
  }
2360
- /*
2361
- TODO: [0] Is there some more elegant way to check circular references?
2362
- const seen = new Set();
2363
- const stack = [{ value }];
2364
- while (stack.length > 0) {
2365
- const { value } = stack.pop()!;
2366
- if (typeof value === 'object' && value !== null) {
2367
- if (seen.has(value)) {
2368
- throw new UnexpectedError(`${name} has circular reference`);
2369
- }
2370
- seen.add(value);
2371
- if (Array.isArray(value)) {
2372
- stack.push(...value.map((value) => ({ value })));
2373
- } else {
2374
- stack.push(...Object.values(value).map((value) => ({ value })));
2375
- }
2376
- }
2601
+ seen.add(value);
2602
+ if (Array.isArray(value)) {
2603
+ stack.push(...value.map((value) => ({ value })));
2604
+ } else {
2605
+ stack.push(...Object.values(value).map((value) => ({ value })));
2377
2606
  }
2378
- */
2379
- return;
2380
2607
  }
2381
2608
  }
2382
- else {
2383
- throw new UnexpectedError(spaceTrim$1((block) => `
2384
- \`${name}\` is unknown type
2609
+ */
2610
+ }
2611
+ /**
2612
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2613
+ *
2614
+ * @private function of `checkSerializableAsJson`
2615
+ */
2616
+ function throwUnknownTypeError(context) {
2617
+ throw new UnexpectedError(spaceTrim$1((block) => `
2618
+ \`${context.name}\` is unknown type
2385
2619
 
2386
- Additional message for \`${name}\`:
2387
- ${block(message || '(nothing)')}
2388
- `));
2389
- }
2620
+ Additional message for \`${context.name}\`:
2621
+ ${block(context.message || '(nothing)')}
2622
+ `));
2390
2623
  }
2391
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2392
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2393
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2394
2624
 
2395
2625
  /**
2396
2626
  * Creates a deep clone of the given object
@@ -2645,233 +2875,518 @@ function validatePipeline(pipeline) {
2645
2875
  */
2646
2876
  function validatePipeline_InnerFunction(pipeline) {
2647
2877
  // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
2648
- const pipelineIdentification = (() => {
2649
- // Note: This is a 😐 implementation of [🚞]
2650
- const _ = [];
2651
- if (pipeline.sourceFile !== undefined) {
2652
- _.push(`File: ${pipeline.sourceFile}`);
2653
- }
2654
- if (pipeline.pipelineUrl !== undefined) {
2655
- _.push(`Url: ${pipeline.pipelineUrl}`);
2878
+ const context = createPipelineValidationContext(pipeline);
2879
+ validatePipelineMetadata(context);
2880
+ validatePipelineCollectionsStructure(context);
2881
+ validatePipelineParameters(context);
2882
+ validatePipelineTasks(context);
2883
+ validatePipelineDependencyResolution(context);
2884
+ // Note: Check that formfactor is corresponding to the pipeline interface
2885
+ // TODO: !!6 Implement this
2886
+ // pipeline.formfactorName
2887
+ }
2888
+ /**
2889
+ * Creates the shared validation context for one pipeline.
2890
+ *
2891
+ * @private internal utility of `validatePipeline`
2892
+ */
2893
+ function createPipelineValidationContext(pipeline) {
2894
+ return {
2895
+ pipeline,
2896
+ pipelineIdentification: getPipelineIdentification(pipeline),
2897
+ };
2898
+ }
2899
+ /**
2900
+ * Builds a short file/url identification block for validation errors.
2901
+ *
2902
+ * @private internal utility of `validatePipeline`
2903
+ */
2904
+ function getPipelineIdentification(pipeline) {
2905
+ // Note: This is a 😐 implementation of [🚞]
2906
+ const pipelineIdentificationParts = [];
2907
+ if (pipeline.sourceFile !== undefined) {
2908
+ pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
2909
+ }
2910
+ if (pipeline.pipelineUrl !== undefined) {
2911
+ pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
2912
+ }
2913
+ return pipelineIdentificationParts.join('\n');
2914
+ }
2915
+ /**
2916
+ * Validates pipeline-level metadata fields.
2917
+ *
2918
+ * @private internal step of `validatePipeline`
2919
+ */
2920
+ function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
2921
+ validatePipelineUrl(pipeline, pipelineIdentification);
2922
+ validatePipelineBookVersion(pipeline, pipelineIdentification);
2923
+ }
2924
+ /**
2925
+ * Validates that the expected top-level collections have array structure.
2926
+ *
2927
+ * @private internal step of `validatePipeline`
2928
+ */
2929
+ function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification }) {
2930
+ validatePipelineParametersCollection(pipeline, pipelineIdentification);
2931
+ validatePipelineTasksCollection(pipeline, pipelineIdentification);
2932
+ }
2933
+ /**
2934
+ * Validates all pipeline parameter declarations.
2935
+ *
2936
+ * @private internal step of `validatePipeline`
2937
+ */
2938
+ function validatePipelineParameters({ pipeline, pipelineIdentification }) {
2939
+ for (const parameter of pipeline.parameters) {
2940
+ validatePipelineParameter(parameter, pipeline, pipelineIdentification);
2941
+ }
2942
+ }
2943
+ /**
2944
+ * Validates all pipeline tasks and their per-task invariants.
2945
+ *
2946
+ * @private internal step of `validatePipeline`
2947
+ */
2948
+ function validatePipelineTasks({ pipeline, pipelineIdentification }) {
2949
+ // Note: All input parameters are defined - so that they can be used as result of some task
2950
+ const definedParameters = createInitiallyDefinedParameters(pipeline);
2951
+ for (const task of pipeline.tasks) {
2952
+ validatePipelineTask(task, definedParameters, pipelineIdentification);
2953
+ }
2954
+ }
2955
+ /**
2956
+ * Validates that task dependencies can be resolved without cycles or missing definitions.
2957
+ *
2958
+ * @private internal step of `validatePipeline`
2959
+ */
2960
+ function validatePipelineDependencyResolution({ pipeline, pipelineIdentification }) {
2961
+ let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
2962
+ let loopLimit = LOOP_LIMIT;
2963
+ while (hasUnresolvedTasks(dependencyResolutionState)) {
2964
+ if (loopLimit-- < 0) {
2965
+ throw createDependencyResolutionLoopLimitError(pipelineIdentification);
2656
2966
  }
2657
- return _.join('\n');
2658
- })();
2659
- if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
2660
- // <- Note: [🚲]
2661
- throw new PipelineLogicError(spaceTrim$1((block) => `
2662
- Invalid promptbook URL "${pipeline.pipelineUrl}"
2663
-
2664
- ${block(pipelineIdentification)}
2665
- `));
2967
+ dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
2666
2968
  }
2667
- if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
2668
- // <- Note: [🚲]
2669
- throw new PipelineLogicError(spaceTrim$1((block) => `
2670
- Invalid Promptbook Version "${pipeline.bookVersion}"
2969
+ }
2970
+ /**
2971
+ * Validates one pipeline parameter declaration.
2972
+ *
2973
+ * @private internal step of `validatePipeline`
2974
+ */
2975
+ function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
2976
+ validateParameterDirection(parameter, pipelineIdentification);
2977
+ validateParameterUsage(parameter, pipeline, pipelineIdentification);
2978
+ validateParameterDefinition(parameter, pipeline, pipelineIdentification);
2979
+ }
2980
+ /**
2981
+ * Validates one pipeline task and its invariants.
2982
+ *
2983
+ * @private internal step of `validatePipeline`
2984
+ */
2985
+ function validatePipelineTask(task, definedParameters, pipelineIdentification) {
2986
+ validateTaskResultingParameter(task, definedParameters, pipelineIdentification);
2987
+ validateTaskJokers(task, pipelineIdentification);
2988
+ validateTaskExpectations(task, pipelineIdentification);
2989
+ }
2990
+ /**
2991
+ * Validates the pipeline URL, when present.
2992
+ *
2993
+ * @private internal utility of `validatePipeline`
2994
+ */
2995
+ function validatePipelineUrl(pipeline, pipelineIdentification) {
2996
+ if (pipeline.pipelineUrl === undefined || isValidPipelineUrl(pipeline.pipelineUrl)) {
2997
+ return;
2998
+ }
2999
+ // <- Note: [🚲]
3000
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3001
+ Invalid promptbook URL "${pipeline.pipelineUrl}"
2671
3002
 
2672
- ${block(pipelineIdentification)}
2673
- `));
3003
+ ${block(pipelineIdentification)}
3004
+ `));
3005
+ }
3006
+ /**
3007
+ * Validates the Promptbook version, when present.
3008
+ *
3009
+ * @private internal utility of `validatePipeline`
3010
+ */
3011
+ function validatePipelineBookVersion(pipeline, pipelineIdentification) {
3012
+ if (pipeline.bookVersion === undefined || isValidPromptbookVersion(pipeline.bookVersion)) {
3013
+ return;
2674
3014
  }
3015
+ // <- Note: [🚲]
3016
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3017
+ Invalid Promptbook Version "${pipeline.bookVersion}"
3018
+
3019
+ ${block(pipelineIdentification)}
3020
+ `));
3021
+ }
3022
+ /**
3023
+ * Validates that `pipeline.parameters` is an array.
3024
+ *
3025
+ * @private internal utility of `validatePipeline`
3026
+ */
3027
+ function validatePipelineParametersCollection(pipeline, pipelineIdentification) {
2675
3028
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2676
- if (!Array.isArray(pipeline.parameters)) {
2677
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2678
- throw new ParseError(spaceTrim$1((block) => `
2679
- Pipeline is valid JSON but with wrong structure
3029
+ if (Array.isArray(pipeline.parameters)) {
3030
+ return;
3031
+ }
3032
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
3033
+ throw new ParseError(spaceTrim$1((block) => `
3034
+ Pipeline is valid JSON but with wrong structure
2680
3035
 
2681
- \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
3036
+ \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2682
3037
 
2683
- ${block(pipelineIdentification)}
2684
- `));
2685
- }
3038
+ ${block(pipelineIdentification)}
3039
+ `));
3040
+ }
3041
+ /**
3042
+ * Validates that `pipeline.tasks` is an array.
3043
+ *
3044
+ * @private internal utility of `validatePipeline`
3045
+ */
3046
+ function validatePipelineTasksCollection(pipeline, pipelineIdentification) {
2686
3047
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2687
- if (!Array.isArray(pipeline.tasks)) {
2688
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2689
- throw new ParseError(spaceTrim$1((block) => `
2690
- Pipeline is valid JSON but with wrong structure
3048
+ if (Array.isArray(pipeline.tasks)) {
3049
+ return;
3050
+ }
3051
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
3052
+ throw new ParseError(spaceTrim$1((block) => `
3053
+ Pipeline is valid JSON but with wrong structure
2691
3054
 
2692
- \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
3055
+ \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2693
3056
 
2694
- ${block(pipelineIdentification)}
2695
- `));
3057
+ ${block(pipelineIdentification)}
3058
+ `));
3059
+ }
3060
+ /**
3061
+ * Validates that one parameter does not declare incompatible directions.
3062
+ *
3063
+ * @private internal utility of `validatePipeline`
3064
+ */
3065
+ function validateParameterDirection(parameter, pipelineIdentification) {
3066
+ if (!parameter.isInput || !parameter.isOutput) {
3067
+ return;
2696
3068
  }
2697
- /*
2698
- TODO: [🧠][🅾] Should be empty pipeline valid or not
2699
- // Note: Check that pipeline has some tasks
2700
- if (pipeline.tasks.length === 0) {
2701
- throw new PipelineLogicError(
2702
- spaceTrim(
2703
- (block) => `
2704
- Pipeline must have at least one task
3069
+ const parameterName = parameter.name;
3070
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2705
3071
 
2706
- ${block(pipelineIdentification)}
2707
- `,
2708
- ),
2709
- );
2710
- }
2711
- */
2712
- // Note: Check each parameter individually
2713
- for (const parameter of pipeline.parameters) {
2714
- if (parameter.isInput && parameter.isOutput) {
2715
- throw new PipelineLogicError(spaceTrim$1((block) => `
3072
+ Parameter \`{${parameterName}}\` can not be both input and output
2716
3073
 
2717
- Parameter \`{${parameter.name}}\` can not be both input and output
3074
+ ${block(pipelineIdentification)}
3075
+ `));
3076
+ }
3077
+ /**
3078
+ * Validates that one intermediate parameter is actually consumed by at least one task.
3079
+ *
3080
+ * @private internal utility of `validatePipeline`
3081
+ */
3082
+ function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
3083
+ if (parameter.isInput || parameter.isOutput || isParameterUsedByAnyTask(parameter, pipeline.tasks)) {
3084
+ return;
3085
+ }
3086
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3087
+ Parameter \`{${parameter.name}}\` is created but not used
2718
3088
 
2719
- ${block(pipelineIdentification)}
2720
- `));
2721
- }
2722
- // Note: Testing that parameter is either intermediate or output BUT not created and unused
2723
- if (!parameter.isInput &&
2724
- !parameter.isOutput &&
2725
- !pipeline.tasks.some((task) => task.dependentParameterNames.includes(parameter.name))) {
2726
- throw new PipelineLogicError(spaceTrim$1((block) => `
2727
- Parameter \`{${parameter.name}}\` is created but not used
3089
+ You can declare {${parameter.name}} as output parameter by adding in the header:
3090
+ - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2728
3091
 
2729
- You can declare {${parameter.name}} as output parameter by adding in the header:
2730
- - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
3092
+ ${block(pipelineIdentification)}
2731
3093
 
2732
- ${block(pipelineIdentification)}
3094
+ `));
3095
+ }
3096
+ /**
3097
+ * Validates that one non-input parameter is produced by at least one task.
3098
+ *
3099
+ * @private internal utility of `validatePipeline`
3100
+ */
3101
+ function validateParameterDefinition(parameter, pipeline, pipelineIdentification) {
3102
+ if (parameter.isInput || isParameterDefinedByAnyTask(parameter, pipeline.tasks)) {
3103
+ return;
3104
+ }
3105
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3106
+ Parameter \`{${parameter.name}}\` is declared but not defined
2733
3107
 
2734
- `));
2735
- }
2736
- // Note: Testing that parameter is either input or result of some task
2737
- if (!parameter.isInput && !pipeline.tasks.some((task) => task.resultingParameterName === parameter.name)) {
2738
- throw new PipelineLogicError(spaceTrim$1((block) => `
2739
- Parameter \`{${parameter.name}}\` is declared but not defined
3108
+ You can do one of these:
3109
+ 1) Remove declaration of \`{${parameter.name}}\`
3110
+ 2) Add task that results in \`-> {${parameter.name}}\`
2740
3111
 
2741
- You can do one of these:
2742
- 1) Remove declaration of \`{${parameter.name}}\`
2743
- 2) Add task that results in \`-> {${parameter.name}}\`
3112
+ ${block(pipelineIdentification)}
3113
+ `));
3114
+ }
3115
+ /**
3116
+ * Checks whether one parameter is consumed by at least one task dependency list.
3117
+ *
3118
+ * @private internal utility of `validatePipeline`
3119
+ */
3120
+ function isParameterUsedByAnyTask(parameter, tasks) {
3121
+ return tasks.some((task) => task.dependentParameterNames.includes(parameter.name));
3122
+ }
3123
+ /**
3124
+ * Checks whether one parameter is produced by at least one task.
3125
+ *
3126
+ * @private internal utility of `validatePipeline`
3127
+ */
3128
+ function isParameterDefinedByAnyTask(parameter, tasks) {
3129
+ return tasks.some((task) => task.resultingParameterName === parameter.name);
3130
+ }
3131
+ /**
3132
+ * Collects the parameter names that are already defined before task validation starts.
3133
+ *
3134
+ * @private internal utility of `validatePipeline`
3135
+ */
3136
+ function createInitiallyDefinedParameters(pipeline) {
3137
+ return new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
3138
+ }
3139
+ /**
3140
+ * Validates one task result parameter declaration and marks it as defined.
3141
+ *
3142
+ * @private internal utility of `validatePipeline`
3143
+ */
3144
+ function validateTaskResultingParameter(task, definedParameters, pipelineIdentification) {
3145
+ if (definedParameters.has(task.resultingParameterName)) {
3146
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3147
+ Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2744
3148
 
2745
- ${block(pipelineIdentification)}
2746
- `));
2747
- }
3149
+ ${block(pipelineIdentification)}
3150
+ `));
2748
3151
  }
2749
- // Note: All input parameters are defined - so that they can be used as result of some task
2750
- const definedParameters = new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2751
- // Note: Checking each task individually
2752
- for (const task of pipeline.tasks) {
2753
- if (definedParameters.has(task.resultingParameterName)) {
2754
- throw new PipelineLogicError(spaceTrim$1((block) => `
2755
- Parameter \`{${task.resultingParameterName}}\` is defined multiple times
3152
+ if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
3153
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3154
+ Parameter name {${task.resultingParameterName}} is reserved, please use different name
2756
3155
 
2757
- ${block(pipelineIdentification)}
2758
- `));
2759
- }
2760
- if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2761
- throw new PipelineLogicError(spaceTrim$1((block) => `
2762
- Parameter name {${task.resultingParameterName}} is reserved, please use different name
3156
+ ${block(pipelineIdentification)}
3157
+ `));
3158
+ }
3159
+ definedParameters.add(task.resultingParameterName);
3160
+ }
3161
+ /**
3162
+ * Validates joker parameters for one task.
3163
+ *
3164
+ * @private internal utility of `validatePipeline`
3165
+ */
3166
+ function validateTaskJokers(task, pipelineIdentification) {
3167
+ if (!hasTaskJokers(task)) {
3168
+ return;
3169
+ }
3170
+ validateTaskSupportsJokers(task, pipelineIdentification);
3171
+ validateTaskJokerDependencies(task, pipelineIdentification);
3172
+ }
3173
+ /**
3174
+ * Checks whether one task declares any joker parameters.
3175
+ *
3176
+ * @private internal utility of `validatePipeline`
3177
+ */
3178
+ function hasTaskJokers(task) {
3179
+ return !!task.jokerParameterNames && task.jokerParameterNames.length > 0;
3180
+ }
3181
+ /**
3182
+ * Validates that a task has the required supporting features when using jokers.
3183
+ *
3184
+ * @private internal utility of `validatePipeline`
3185
+ */
3186
+ function validateTaskSupportsJokers(task, pipelineIdentification) {
3187
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
3188
+ return;
3189
+ }
3190
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3191
+ Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2763
3192
 
2764
- ${block(pipelineIdentification)}
2765
- `));
3193
+ ${block(pipelineIdentification)}
3194
+ `));
3195
+ }
3196
+ /**
3197
+ * Validates that every joker parameter is also listed among task dependencies.
3198
+ *
3199
+ * @private internal utility of `validatePipeline`
3200
+ */
3201
+ function validateTaskJokerDependencies(task, pipelineIdentification) {
3202
+ for (const joker of task.jokerParameterNames) {
3203
+ if (task.dependentParameterNames.includes(joker)) {
3204
+ continue;
2766
3205
  }
2767
- definedParameters.add(task.resultingParameterName);
2768
- if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
2769
- if (!task.format &&
2770
- !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2771
- throw new PipelineLogicError(spaceTrim$1((block) => `
2772
- Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2773
-
2774
- ${block(pipelineIdentification)}
2775
- `));
2776
- }
2777
- for (const joker of task.jokerParameterNames) {
2778
- if (!task.dependentParameterNames.includes(joker)) {
2779
- throw new PipelineLogicError(spaceTrim$1((block) => `
2780
- Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
3206
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3207
+ Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2781
3208
 
2782
- ${block(pipelineIdentification)}
2783
- `));
2784
- }
2785
- }
2786
- }
2787
- if (task.expectations) {
2788
- for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2789
- if (min !== undefined && max !== undefined && min > max) {
2790
- throw new PipelineLogicError(spaceTrim$1((block) => `
2791
- Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
3209
+ ${block(pipelineIdentification)}
3210
+ `));
3211
+ }
3212
+ }
3213
+ /**
3214
+ * Validates all expectation bounds configured on one task.
3215
+ *
3216
+ * @private internal utility of `validatePipeline`
3217
+ */
3218
+ function validateTaskExpectations(task, pipelineIdentification) {
3219
+ if (!task.expectations) {
3220
+ return;
3221
+ }
3222
+ for (const [unit, { min, max }] of Object.entries(task.expectations)) {
3223
+ validateTaskExpectationRange(unit, min, max, pipelineIdentification);
3224
+ validateTaskExpectationMin(unit, min, pipelineIdentification);
3225
+ validateTaskExpectationMax(unit, max, pipelineIdentification);
3226
+ }
3227
+ }
3228
+ /**
3229
+ * Validates the minimum and maximum expectation ordering for one unit.
3230
+ *
3231
+ * @private internal utility of `validatePipeline`
3232
+ */
3233
+ function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
3234
+ if (min === undefined || max === undefined || min <= max) {
3235
+ return;
3236
+ }
3237
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3238
+ Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2792
3239
 
2793
- ${block(pipelineIdentification)}
2794
- `));
2795
- }
2796
- if (min !== undefined && min < 0) {
2797
- throw new PipelineLogicError(spaceTrim$1((block) => `
2798
- Min expectation of ${unit} must be zero or positive
3240
+ ${block(pipelineIdentification)}
3241
+ `));
3242
+ }
3243
+ /**
3244
+ * Validates the minimum expectation bound for one unit.
3245
+ *
3246
+ * @private internal utility of `validatePipeline`
3247
+ */
3248
+ function validateTaskExpectationMin(unit, min, pipelineIdentification) {
3249
+ if (min === undefined || min >= 0) {
3250
+ return;
3251
+ }
3252
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3253
+ Min expectation of ${unit} must be zero or positive
2799
3254
 
2800
- ${block(pipelineIdentification)}
2801
- `));
2802
- }
2803
- if (max !== undefined && max <= 0) {
2804
- throw new PipelineLogicError(spaceTrim$1((block) => `
2805
- Max expectation of ${unit} must be positive
3255
+ ${block(pipelineIdentification)}
3256
+ `));
3257
+ }
3258
+ /**
3259
+ * Validates the maximum expectation bound for one unit.
3260
+ *
3261
+ * @private internal utility of `validatePipeline`
3262
+ */
3263
+ function validateTaskExpectationMax(unit, max, pipelineIdentification) {
3264
+ if (max === undefined || max > 0) {
3265
+ return;
3266
+ }
3267
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3268
+ Max expectation of ${unit} must be positive
2806
3269
 
2807
- ${block(pipelineIdentification)}
2808
- `));
2809
- }
2810
- }
2811
- }
3270
+ ${block(pipelineIdentification)}
3271
+ `));
3272
+ }
3273
+ /**
3274
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
3275
+ *
3276
+ * @private internal utility of `validatePipeline`
3277
+ */
3278
+ function createInitialDependencyResolutionState(pipeline) {
3279
+ return {
3280
+ resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
3281
+ unresolvedTasks: [...pipeline.tasks],
3282
+ };
3283
+ }
3284
+ /**
3285
+ * Checks whether dependency resolution still has tasks left to process.
3286
+ *
3287
+ * @private internal utility of `validatePipeline`
3288
+ */
3289
+ function hasUnresolvedTasks({ unresolvedTasks }) {
3290
+ return unresolvedTasks.length > 0;
3291
+ }
3292
+ /**
3293
+ * Resolves the next batch of currently satisfiable tasks.
3294
+ *
3295
+ * @private internal utility of `validatePipeline`
3296
+ */
3297
+ function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
3298
+ const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
3299
+ if (currentlyResolvedTasks.length === 0) {
3300
+ throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
2812
3301
  }
2813
- // Note: Detect circular dependencies
2814
- let resovedParameters = pipeline.parameters
3302
+ return {
3303
+ resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
3304
+ unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
3305
+ };
3306
+ }
3307
+ /**
3308
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
3309
+ *
3310
+ * @private internal utility of `validatePipeline`
3311
+ */
3312
+ function createInitiallyResolvedParameterNames(pipeline) {
3313
+ let resolvedParameterNames = pipeline.parameters
2815
3314
  .filter(({ isInput }) => isInput)
2816
3315
  .map(({ name }) => name);
2817
- // Note: All reserved parameters are resolved
2818
3316
  for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
2819
- resovedParameters = [...resovedParameters, reservedParameterName];
3317
+ resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
2820
3318
  }
2821
- let unresovedTasks = [...pipeline.tasks];
2822
- let loopLimit = LOOP_LIMIT;
2823
- while (unresovedTasks.length > 0) {
2824
- if (loopLimit-- < 0) {
2825
- // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2826
- throw new UnexpectedError(spaceTrim$1((block) => `
2827
- Loop limit reached during detection of circular dependencies in \`validatePipeline\`
3319
+ return resolvedParameterNames;
3320
+ }
3321
+ /**
3322
+ * Adds newly resolved task outputs to the resolved parameter list.
3323
+ *
3324
+ * @private internal utility of `validatePipeline`
3325
+ */
3326
+ function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
3327
+ return [
3328
+ ...resolvedParameterNames,
3329
+ ...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
3330
+ ];
3331
+ }
3332
+ /**
3333
+ * Selects tasks whose dependencies are already resolved.
3334
+ *
3335
+ * @private internal utility of `validatePipeline`
3336
+ */
3337
+ function getCurrentlyResolvedTasks(unresolvedTasks, resolvedParameterNames) {
3338
+ return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
3339
+ }
3340
+ /**
3341
+ * Creates the unexpected loop-limit error for dependency resolution.
3342
+ *
3343
+ * @private internal utility of `validatePipeline`
3344
+ */
3345
+ function createDependencyResolutionLoopLimitError(pipelineIdentification) {
3346
+ // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
3347
+ return new UnexpectedError(spaceTrim$1((block) => `
3348
+ Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2828
3349
 
2829
- ${block(pipelineIdentification)}
2830
- `));
2831
- }
2832
- const currentlyResovedTasks = unresovedTasks.filter((task) => task.dependentParameterNames.every((name) => resovedParameters.includes(name)));
2833
- if (currentlyResovedTasks.length === 0) {
2834
- throw new PipelineLogicError(
2835
- // TODO: [🐎] DRY
2836
- spaceTrim$1((block) => `
3350
+ ${block(pipelineIdentification)}
3351
+ `));
3352
+ }
3353
+ /**
3354
+ * Creates the detailed error for unresolved or circular task dependencies.
3355
+ *
3356
+ * @private internal utility of `validatePipeline`
3357
+ */
3358
+ function createUnresolvedTasksError(unresolvedTasks, resolvedParameterNames, pipelineIdentification) {
3359
+ return new PipelineLogicError(
3360
+ // TODO: [🐎] DRY
3361
+ spaceTrim$1((block) => `
2837
3362
 
2838
- Can not resolve some parameters:
2839
- Either you are using a parameter that is not defined, or there are some circular dependencies.
3363
+ Can not resolve some parameters:
3364
+ Either you are using a parameter that is not defined, or there are some circular dependencies.
2840
3365
 
2841
- ${block(pipelineIdentification)}
3366
+ ${block(pipelineIdentification)}
2842
3367
 
2843
- **Can not resolve:**
2844
- ${block(unresovedTasks
2845
- .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2846
- .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2847
- .join(' and ')}`)
2848
- .join('\n'))}
3368
+ **Can not resolve:**
3369
+ ${block(unresolvedTasks
3370
+ .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
3371
+ .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
3372
+ .join(' and ')}`)
3373
+ .join('\n'))}
2849
3374
 
2850
- **Resolved:**
2851
- ${block(resovedParameters
2852
- .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2853
- .map((name) => `- Parameter \`{${name}}\``)
2854
- .join('\n'))}
3375
+ **Resolved:**
3376
+ ${block(resolvedParameterNames
3377
+ .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
3378
+ .map((name) => `- Parameter \`{${name}}\``)
3379
+ .join('\n'))}
2855
3380
 
2856
3381
 
2857
- **Reserved (which are available):**
2858
- ${block(resovedParameters
2859
- .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2860
- .map((name) => `- Parameter \`{${name}}\``)
2861
- .join('\n'))}
3382
+ **Reserved (which are available):**
3383
+ ${block(resolvedParameterNames
3384
+ .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
3385
+ .map((name) => `- Parameter \`{${name}}\``)
3386
+ .join('\n'))}
2862
3387
 
2863
3388
 
2864
- `));
2865
- }
2866
- resovedParameters = [
2867
- ...resovedParameters,
2868
- ...currentlyResovedTasks.map(({ resultingParameterName }) => resultingParameterName),
2869
- ];
2870
- unresovedTasks = unresovedTasks.filter((task) => !currentlyResovedTasks.includes(task));
2871
- }
2872
- // Note: Check that formfactor is corresponding to the pipeline interface
2873
- // TODO: !!6 Implement this
2874
- // pipeline.formfactorName
3389
+ `));
2875
3390
  }
2876
3391
  /**
2877
3392
  * TODO: [🧞‍♀️] Do not allow joker + foreach
@@ -3567,70 +4082,281 @@ function assertsTaskSuccessful(executionResult) {
3567
4082
  // TODO: [🧠] Can this return type be better typed than void
3568
4083
 
3569
4084
  /**
3570
- * Helper to create a new task
4085
+ * Resolves the short task summary shown in the UI.
4086
+ *
4087
+ * @private internal helper function of `ExecutionTask`
4088
+ */
4089
+ function resolveTaskTldr(options) {
4090
+ const { customTldr } = options;
4091
+ if (customTldr) {
4092
+ return customTldr;
4093
+ }
4094
+ return {
4095
+ percent: resolveTaskPercent(options),
4096
+ message: `${resolveTaskMessage(options)} (!!!fallback)`,
4097
+ };
4098
+ }
4099
+ /**
4100
+ * Resolves the best progress percentage for the current task state.
4101
+ *
4102
+ * @private internal helper function of `ExecutionTask`
4103
+ */
4104
+ function resolveTaskPercent(options) {
4105
+ const explicitPercent = getExplicitTaskPercent(options.currentValue);
4106
+ if (typeof explicitPercent === 'number') {
4107
+ return normalizeTaskPercent(explicitPercent);
4108
+ }
4109
+ return normalizeTaskPercent(calculateSimulatedTaskPercent(options));
4110
+ }
4111
+ /**
4112
+ * Picks a directly reported progress percentage from the task result snapshot.
4113
+ *
4114
+ * @private internal helper function of `ExecutionTask`
4115
+ */
4116
+ function getExplicitTaskPercent(currentValue) {
4117
+ var _a, _b, _c, _d, _e, _f;
4118
+ 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);
4119
+ }
4120
+ /**
4121
+ * Simulates progress when the task result does not expose an explicit percentage.
4122
+ *
4123
+ * @private internal helper function of `ExecutionTask`
4124
+ */
4125
+ function calculateSimulatedTaskPercent(options) {
4126
+ const { currentValue, status, createdAt } = options;
4127
+ const elapsedMs = new Date().getTime() - createdAt.getTime();
4128
+ const timeProgress = Math.min(elapsedMs / DEFAULT_TASK_SIMULATED_DURATION_MS, 1);
4129
+ const { subtaskCount, completedSubtasks } = summarizeTaskSubtasks(currentValue);
4130
+ if (status === 'FINISHED') {
4131
+ return 1;
4132
+ }
4133
+ if (status === 'ERROR') {
4134
+ return 0;
4135
+ }
4136
+ return Math.min(completedSubtasks / subtaskCount + (1 / subtaskCount) * timeProgress, 1);
4137
+ }
4138
+ /**
4139
+ * Counts total and completed subtasks used by the fallback progress simulation.
4140
+ *
4141
+ * @private internal helper function of `ExecutionTask`
4142
+ */
4143
+ function summarizeTaskSubtasks(currentValue) {
4144
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks)) {
4145
+ return { subtaskCount: 1, completedSubtasks: 0 };
4146
+ }
4147
+ return {
4148
+ subtaskCount: currentValue.subtasks.length || 1,
4149
+ completedSubtasks: currentValue.subtasks.filter(isTaskSubtaskCompleted).length,
4150
+ };
4151
+ }
4152
+ /**
4153
+ * Tells whether a task subtask is already finished.
4154
+ *
4155
+ * @private internal helper function of `ExecutionTask`
4156
+ */
4157
+ function isTaskSubtaskCompleted(subtask) {
4158
+ return subtask.done || subtask.completed || false;
4159
+ }
4160
+ /**
4161
+ * Normalizes a progress percentage into the expected `0..1` range.
4162
+ *
4163
+ * @private internal helper function of `ExecutionTask`
4164
+ */
4165
+ function normalizeTaskPercent(percentRaw) {
4166
+ let percent = Number(percentRaw) || 0;
4167
+ if (percent < 0) {
4168
+ percent = 0;
4169
+ }
4170
+ if (percent > 1) {
4171
+ percent = 1;
4172
+ }
4173
+ return percent;
4174
+ }
4175
+ /**
4176
+ * Resolves the best human-readable status message for the current task state.
4177
+ *
4178
+ * @private internal helper function of `ExecutionTask`
4179
+ */
4180
+ function resolveTaskMessage(options) {
4181
+ return (getCurrentValueMessage(options.currentValue) ||
4182
+ getCurrentSubtaskMessage(options.currentValue) ||
4183
+ getLatestIssueMessage(options.errors, 'Error') ||
4184
+ getLatestIssueMessage(options.warnings, 'Warning') ||
4185
+ getStatusMessage(options.status));
4186
+ }
4187
+ /**
4188
+ * Picks a message already reported by the current task result snapshot.
4189
+ *
4190
+ * @private internal helper function of `ExecutionTask`
4191
+ */
4192
+ function getCurrentValueMessage(currentValue) {
4193
+ var _a, _b, _c, _d;
4194
+ 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;
4195
+ }
4196
+ /**
4197
+ * Builds a fallback message from the first unfinished subtask title.
4198
+ *
4199
+ * @private internal helper function of `ExecutionTask`
4200
+ */
4201
+ function getCurrentSubtaskMessage(currentValue) {
4202
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks) || currentValue.subtasks.length === 0) {
4203
+ return undefined;
4204
+ }
4205
+ const currentSubtask = currentValue.subtasks.find((subtask) => !isTaskSubtaskCompleted(subtask));
4206
+ if (!(currentSubtask === null || currentSubtask === void 0 ? void 0 : currentSubtask.title)) {
4207
+ return undefined;
4208
+ }
4209
+ return `Working on ${currentSubtask.title}`;
4210
+ }
4211
+ /**
4212
+ * Picks the latest error or warning message, with the legacy generic fallback label.
4213
+ *
4214
+ * @private internal helper function of `ExecutionTask`
4215
+ */
4216
+ function getLatestIssueMessage(issues, fallbackMessage) {
4217
+ if (issues.length === 0) {
4218
+ return undefined;
4219
+ }
4220
+ return issues[issues.length - 1].message || fallbackMessage;
4221
+ }
4222
+ /**
4223
+ * Builds the final status-based fallback message.
4224
+ *
4225
+ * @private internal helper function of `ExecutionTask`
4226
+ */
4227
+ function getStatusMessage(status) {
4228
+ if (status === 'FINISHED') {
4229
+ return 'Finished';
4230
+ }
4231
+ if (status === 'ERROR') {
4232
+ return 'Error';
4233
+ }
4234
+ return 'Running';
4235
+ }
4236
+
4237
+ /**
4238
+ * Creates the initial mutable state for a task.
3571
4239
  *
3572
4240
  * @private internal helper function
3573
4241
  */
3574
- function createTask(options) {
3575
- const { taskType, taskProcessCallback } = options;
3576
- let { title } = options;
3577
- // TODO: [🐙] DRY
3578
- const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3579
- let status = 'RUNNING';
3580
- const createdAt = new Date();
3581
- let updatedAt = createdAt;
3582
- const errors = [];
3583
- const warnings = [];
3584
- const llmCalls = [];
3585
- let currentValue = {};
3586
- let customTldr = null;
3587
- const partialResultSubject = new Subject();
3588
- // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3589
- const finalResultPromise = /* not await */ taskProcessCallback((newOngoingResult) => {
4242
+ function createTaskState(title, createdAt) {
4243
+ return {
4244
+ title,
4245
+ status: 'RUNNING',
4246
+ updatedAt: createdAt,
4247
+ errors: [],
4248
+ warnings: [],
4249
+ llmCalls: [],
4250
+ currentValue: {},
4251
+ customTldr: null,
4252
+ };
4253
+ }
4254
+ /**
4255
+ * Creates the partial-result updater passed into the task process callback.
4256
+ *
4257
+ * @private internal helper function
4258
+ */
4259
+ function createOngoingResultUpdater(taskState, partialResultSubject) {
4260
+ return (newOngoingResult) => {
3590
4261
  if (newOngoingResult.title) {
3591
- title = newOngoingResult.title;
4262
+ taskState.title = newOngoingResult.title;
3592
4263
  }
3593
- updatedAt = new Date();
3594
- Object.assign(currentValue, newOngoingResult);
4264
+ taskState.updatedAt = new Date();
4265
+ Object.assign(taskState.currentValue, newOngoingResult);
3595
4266
  // <- TODO: assign deep
3596
4267
  partialResultSubject.next(newOngoingResult);
3597
- }, (tldrInfo) => {
3598
- customTldr = tldrInfo;
3599
- updatedAt = new Date();
3600
- }, (llmCall) => {
3601
- llmCalls.push(llmCall);
3602
- updatedAt = new Date();
3603
- });
4268
+ };
4269
+ }
4270
+ /**
4271
+ * Creates the custom-TLDR updater passed into the task process callback.
4272
+ *
4273
+ * @private internal helper function
4274
+ */
4275
+ function createTldrUpdater(taskState) {
4276
+ return (tldrInfo) => {
4277
+ taskState.customTldr = tldrInfo;
4278
+ taskState.updatedAt = new Date();
4279
+ };
4280
+ }
4281
+ /**
4282
+ * Creates the LLM call logger passed into the task process callback.
4283
+ *
4284
+ * @private internal helper function
4285
+ */
4286
+ function createLlmCallLogger(taskState) {
4287
+ return (llmCall) => {
4288
+ taskState.llmCalls.push(llmCall);
4289
+ taskState.updatedAt = new Date();
4290
+ };
4291
+ }
4292
+ /**
4293
+ * Wires the task promise into the observable/error lifecycle.
4294
+ *
4295
+ * @private internal helper function
4296
+ */
4297
+ function settleTaskPromise(finalResultPromise, taskState, partialResultSubject) {
3604
4298
  finalResultPromise
3605
4299
  .catch((error) => {
3606
- errors.push(error);
4300
+ taskState.errors.push(error);
3607
4301
  partialResultSubject.error(error);
3608
4302
  })
3609
4303
  .then((executionResult) => {
3610
4304
  if (executionResult) {
3611
4305
  try {
3612
- updatedAt = new Date();
3613
- errors.push(...executionResult.errors);
3614
- warnings.push(...executionResult.warnings);
3615
- // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3616
- // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3617
- // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3618
- // And delete `ExecutionTask.currentValue.preparedPipeline`
3619
- assertsTaskSuccessful(executionResult);
3620
- status = 'FINISHED';
3621
- currentValue = jsonStringsToJsons(executionResult);
3622
- // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3623
- partialResultSubject.next(executionResult);
4306
+ finalizeTaskResult(executionResult, taskState, partialResultSubject);
3624
4307
  }
3625
4308
  catch (error) {
3626
- assertsError(error);
3627
- status = 'ERROR';
3628
- errors.push(error);
3629
- partialResultSubject.error(error);
4309
+ failTaskResult(error, taskState, partialResultSubject);
3630
4310
  }
3631
4311
  }
3632
4312
  partialResultSubject.complete();
3633
4313
  });
4314
+ }
4315
+ /**
4316
+ * Applies the final successful task result into the mutable task state.
4317
+ *
4318
+ * @private internal helper function
4319
+ */
4320
+ function finalizeTaskResult(executionResult, taskState, partialResultSubject) {
4321
+ taskState.updatedAt = new Date();
4322
+ taskState.errors.push(...executionResult.errors);
4323
+ taskState.warnings.push(...executionResult.warnings);
4324
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
4325
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
4326
+ // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
4327
+ // And delete `ExecutionTask.currentValue.preparedPipeline`
4328
+ assertsTaskSuccessful(executionResult);
4329
+ taskState.status = 'FINISHED';
4330
+ taskState.currentValue = jsonStringsToJsons(executionResult);
4331
+ // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
4332
+ partialResultSubject.next(executionResult);
4333
+ }
4334
+ /**
4335
+ * Records a final-result failure after the task promise itself resolved.
4336
+ *
4337
+ * @private internal helper function
4338
+ */
4339
+ function failTaskResult(error, taskState, partialResultSubject) {
4340
+ assertsError(error);
4341
+ taskState.status = 'ERROR';
4342
+ taskState.errors.push(error);
4343
+ partialResultSubject.error(error);
4344
+ }
4345
+ /**
4346
+ * Helper to create a new task
4347
+ *
4348
+ * @private internal helper function
4349
+ */
4350
+ function createTask(options) {
4351
+ const { taskType, title, taskProcessCallback } = options;
4352
+ // TODO: [🐙] DRY
4353
+ const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
4354
+ const createdAt = new Date();
4355
+ const taskState = createTaskState(title, createdAt);
4356
+ const partialResultSubject = new Subject();
4357
+ // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
4358
+ const finalResultPromise = /* not await */ taskProcessCallback(createOngoingResultUpdater(taskState, partialResultSubject), createTldrUpdater(taskState), createLlmCallLogger(taskState));
4359
+ settleTaskPromise(finalResultPromise, taskState, partialResultSubject);
3634
4360
  async function asPromise(options) {
3635
4361
  const { isCrashedOnError = true } = options || {};
3636
4362
  const finalResult = await finalResultPromise;
@@ -3646,91 +4372,29 @@ function createTask(options) {
3646
4372
  return PROMPTBOOK_ENGINE_VERSION;
3647
4373
  },
3648
4374
  get title() {
3649
- return title;
4375
+ return taskState.title;
3650
4376
  // <- Note: [1] These must be getters to allow changing the value in the future
3651
4377
  },
3652
4378
  get status() {
3653
- return status;
4379
+ return taskState.status;
3654
4380
  // <- Note: [1] --||--
3655
4381
  },
3656
4382
  get tldr() {
3657
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3658
- // Use custom tldr if available
3659
- if (customTldr) {
3660
- return customTldr;
3661
- }
3662
- // Fallback to default implementation
3663
- const cv = currentValue;
3664
- // If explicit percent is provided, use it
3665
- 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;
3666
- // Simulate progress if not provided
3667
- if (typeof percentRaw !== 'number') {
3668
- // Simulate progress: evenly split across subtasks, based on elapsed time
3669
- const now = new Date();
3670
- const elapsedMs = now.getTime() - createdAt.getTime();
3671
- const totalMs = DEFAULT_TASK_SIMULATED_DURATION_MS;
3672
- // If subtasks are defined, split progress evenly
3673
- const subtaskCount = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) ? cv.subtasks.length : 1;
3674
- const completedSubtasks = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks)
3675
- ? cv.subtasks.filter((s) => s.done || s.completed).length
3676
- : 0;
3677
- // Progress from completed subtasks
3678
- const subtaskProgress = subtaskCount > 0 ? completedSubtasks / subtaskCount : 0;
3679
- // Progress from elapsed time for current subtask
3680
- const timeProgress = Math.min(elapsedMs / totalMs, 1);
3681
- // Combine: completed subtasks + time progress for current subtask
3682
- percentRaw = Math.min(subtaskProgress + (1 / subtaskCount) * timeProgress, 1);
3683
- if (status === 'FINISHED')
3684
- percentRaw = 1;
3685
- if (status === 'ERROR')
3686
- percentRaw = 0;
3687
- }
3688
- // Clamp to [0,1]
3689
- let percent = Number(percentRaw) || 0;
3690
- if (percent < 0)
3691
- percent = 0;
3692
- if (percent > 1)
3693
- percent = 1;
3694
- // Build a short message: prefer explicit tldr.message, then common summary/message fields, then errors/warnings, then status
3695
- 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;
3696
- let message = messageFromResult;
3697
- if (!message) {
3698
- // If subtasks, show current subtask
3699
- if (Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) && cv.subtasks.length > 0) {
3700
- const current = cv.subtasks.find((s) => !s.done && !s.completed);
3701
- if (current && current.title) {
3702
- message = `Working on ${current.title}`;
3703
- }
3704
- }
3705
- if (!message) {
3706
- if (errors.length) {
3707
- message = errors[errors.length - 1].message || 'Error';
3708
- }
3709
- else if (warnings.length) {
3710
- message = warnings[warnings.length - 1].message || 'Warning';
3711
- }
3712
- else if (status === 'FINISHED') {
3713
- message = 'Finished';
3714
- }
3715
- else if (status === 'ERROR') {
3716
- message = 'Error';
3717
- }
3718
- else {
3719
- message = 'Running';
3720
- }
3721
- }
3722
- }
3723
- return {
3724
- percent: percent,
3725
- message: message + ' (!!!fallback)',
3726
- };
4383
+ return resolveTaskTldr({
4384
+ customTldr: taskState.customTldr,
4385
+ currentValue: taskState.currentValue,
4386
+ status: taskState.status,
4387
+ createdAt,
4388
+ errors: taskState.errors,
4389
+ warnings: taskState.warnings,
4390
+ });
3727
4391
  },
3728
4392
  get createdAt() {
3729
4393
  return createdAt;
3730
4394
  // <- Note: [1] --||--
3731
4395
  },
3732
4396
  get updatedAt() {
3733
- return updatedAt;
4397
+ return taskState.updatedAt;
3734
4398
  // <- Note: [1] --||--
3735
4399
  },
3736
4400
  asPromise,
@@ -3738,19 +4402,19 @@ function createTask(options) {
3738
4402
  return partialResultSubject.asObservable();
3739
4403
  },
3740
4404
  get errors() {
3741
- return errors;
4405
+ return taskState.errors;
3742
4406
  // <- Note: [1] --||--
3743
4407
  },
3744
4408
  get warnings() {
3745
- return warnings;
4409
+ return taskState.warnings;
3746
4410
  // <- Note: [1] --||--
3747
4411
  },
3748
4412
  get llmCalls() {
3749
- return [...llmCalls, { foo: '!!! bar' }];
4413
+ return [...taskState.llmCalls, { foo: '!!! bar' }];
3750
4414
  // <- Note: [1] --||--
3751
4415
  },
3752
4416
  get currentValue() {
3753
- return currentValue;
4417
+ return taskState.currentValue;
3754
4418
  // <- Note: [1] --||--
3755
4419
  },
3756
4420
  };
@@ -4668,210 +5332,275 @@ const promptbookFetch = async (urlOrRequest, init) => {
4668
5332
  * @public exported from `@promptbook/core`
4669
5333
  */
4670
5334
  async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
5335
+ const { knowledgeSourceContent } = knowledgeSource;
5336
+ const name = knowledgeSource.name || knowledgeSourceContentToName(knowledgeSourceContent);
5337
+ const { rootDirname = null, isVerbose = DEFAULT_IS_VERBOSE } = options || {};
5338
+ if (isValidUrl(knowledgeSourceContent)) {
5339
+ return makeUrlKnowledgeSourceHandler(knowledgeSourceContent, name, tools, options, isVerbose);
5340
+ }
5341
+ if (isValidFilePath(knowledgeSourceContent)) {
5342
+ return makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose);
5343
+ }
5344
+ return makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose);
5345
+ }
5346
+ /**
5347
+ * Creates a source handler for URL-based knowledge.
5348
+ *
5349
+ * @private internal utility of `makeKnowledgeSourceHandler`
5350
+ */
5351
+ async function makeUrlKnowledgeSourceHandler(url, name, tools, options, isVerbose) {
4671
5352
  var _a;
4672
5353
  const { fetch = promptbookFetch } = tools;
4673
- const { knowledgeSourceContent } = knowledgeSource;
4674
- let { name } = knowledgeSource;
4675
- const { rootDirname = null,
4676
- // <- TODO: process.cwd() if running in Node.js
4677
- isVerbose = DEFAULT_IS_VERBOSE, } = options || {};
4678
- if (!name) {
4679
- name = knowledgeSourceContentToName(knowledgeSourceContent);
5354
+ if (isVerbose) {
5355
+ console.info(`📄 [1] "${name}" is available at "${url}"`);
4680
5356
  }
4681
- if (isValidUrl(knowledgeSourceContent)) {
4682
- const url = knowledgeSourceContent;
4683
- if (isVerbose) {
4684
- console.info(`📄 [1] "${name}" is available at "${url}"`);
4685
- }
4686
- const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
4687
- const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
4688
- if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
4689
- if (isVerbose) {
4690
- console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
4691
- }
4692
- return {
4693
- source: name,
4694
- filename: null,
4695
- url,
4696
- mimeType,
4697
- /*
4698
- TODO: [🥽]
4699
- > async asBlob() {
4700
- > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
4701
- > const content = await response.blob();
4702
- > return content;
4703
- > },
4704
- */
4705
- async asJson() {
4706
- // TODO: [👨🏻‍🤝‍👨🏻]
4707
- const content = await response.json();
4708
- return content;
4709
- },
4710
- async asText() {
4711
- // TODO: [👨🏻‍🤝‍👨🏻]
4712
- const content = await response.text();
4713
- return content;
4714
- },
4715
- };
4716
- }
4717
- const basename = url.split('/').pop() || titleToName(url);
4718
- const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
4719
- // <- TODO: [🥬] Encapsulate sha256 to some private utility function
4720
- const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
4721
- const filepath = join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
4722
- // Note: Try to create cache directory, but don't fail if filesystem has issues
4723
- try {
4724
- await tools.fs.mkdir(dirname(join(rootDirname, filepath)), { recursive: true });
4725
- }
4726
- catch (error) {
4727
- if (isVerbose) {
4728
- console.info(`📄 [3] "${name}" error creating cache directory`);
4729
- }
4730
- // Note: If we can't create cache directory, we'll handle it when trying to write the file
4731
- // This handles read-only filesystems, permission issues, and missing parent directories
4732
- if (error instanceof Error &&
4733
- (error.message.includes('EROFS') ||
4734
- error.message.includes('read-only') ||
4735
- error.message.includes('EACCES') ||
4736
- error.message.includes('EPERM') ||
4737
- error.message.includes('ENOENT'))) ;
4738
- else {
4739
- // Re-throw other unexpected errors
4740
- throw error;
4741
- }
4742
- }
4743
- const fileContent = Buffer.from(await response.arrayBuffer());
4744
- if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
4745
- 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.`);
4746
- }
4747
- // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
4748
- try {
4749
- await tools.fs.writeFile(join(rootDirname, filepath), fileContent);
4750
- }
4751
- catch (error) {
4752
- if (isVerbose) {
4753
- console.info(`📄 [4] "${name}" error writing cache file`);
4754
- }
4755
- // Note: If we can't write to cache, we'll process the file directly from memory
4756
- // This handles read-only filesystems like Vercel
4757
- if (error instanceof Error &&
4758
- (error.message.includes('EROFS') ||
4759
- error.message.includes('read-only') ||
4760
- error.message.includes('EACCES') ||
4761
- error.message.includes('EPERM') ||
4762
- error.message.includes('ENOENT'))) {
4763
- // Return a handler that works directly with the downloaded content
4764
- return {
4765
- source: name,
4766
- filename: null,
4767
- url,
4768
- mimeType,
4769
- async asJson() {
4770
- return JSON.parse(fileContent.toString('utf-8'));
4771
- },
4772
- async asText() {
4773
- return fileContent.toString('utf-8');
4774
- },
4775
- };
4776
- }
4777
- else {
4778
- // Re-throw other unexpected errors
4779
- throw error;
4780
- }
4781
- }
4782
- // TODO: [💵] Check the file security
4783
- // TODO: [🧹][🧠] Delete the file after the scraping is done
4784
- if (isVerbose) {
4785
- console.info(`📄 [5] "${name}" cached at "${join(rootDirname, filepath)}"`);
4786
- }
4787
- return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
4788
- ...options,
4789
- rootDirname,
4790
- });
5357
+ const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
5358
+ const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
5359
+ if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
5360
+ return makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose);
4791
5361
  }
4792
- else if (isValidFilePath(knowledgeSourceContent)) {
4793
- if (tools.fs === undefined) {
4794
- throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
4795
- // <- TODO: [🧠] What is the best error type here`
5362
+ return cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose);
5363
+ }
5364
+ /**
5365
+ * Creates a source handler that reads directly from a fetched response.
5366
+ *
5367
+ * @private internal utility of `makeKnowledgeSourceHandler`
5368
+ */
5369
+ function makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose) {
5370
+ if (isVerbose) {
5371
+ console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
5372
+ }
5373
+ return {
5374
+ source: name,
5375
+ filename: null,
5376
+ url,
5377
+ mimeType,
5378
+ /*
5379
+ TODO: [🥽]
5380
+ > async asBlob() {
5381
+ > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
5382
+ > const content = await response.blob();
5383
+ > return content;
5384
+ > },
5385
+ */
5386
+ async asJson() {
5387
+ // TODO: [👨🏻‍🤝‍👨🏻]
5388
+ const content = await response.json();
5389
+ return content;
5390
+ },
5391
+ async asText() {
5392
+ // TODO: [👨🏻‍🤝‍👨🏻]
5393
+ const content = await response.text();
5394
+ return content;
5395
+ },
5396
+ };
5397
+ }
5398
+ /**
5399
+ * Downloads a PDF knowledge source into cache when possible and falls back to in-memory content otherwise.
5400
+ *
5401
+ * @private internal utility of `makeKnowledgeSourceHandler`
5402
+ */
5403
+ async function cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose) {
5404
+ const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5405
+ const filepath = createDownloadedKnowledgeSourceFilepath(url, mimeType);
5406
+ const fullFilepath = join(rootDirname, filepath);
5407
+ await createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose);
5408
+ const fileContent = Buffer.from(await response.arrayBuffer());
5409
+ if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5410
+ 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.`);
5411
+ }
5412
+ const isCached = await writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose);
5413
+ if (!isCached) {
5414
+ return makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent);
5415
+ }
5416
+ // TODO: [💵] Check the file security
5417
+ // TODO: [🧹][🧠] Delete the file after the scraping is done
5418
+ if (isVerbose) {
5419
+ console.info(`📄 [5] "${name}" cached at "${fullFilepath}"`);
5420
+ }
5421
+ return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
5422
+ ...options,
5423
+ rootDirname,
5424
+ });
5425
+ }
5426
+ /**
5427
+ * Builds a stable cache filepath for a downloaded knowledge source.
5428
+ *
5429
+ * @private internal utility of `makeKnowledgeSourceHandler`
5430
+ */
5431
+ function createDownloadedKnowledgeSourceFilepath(url, mimeType) {
5432
+ const basename = url.split('/').pop() || titleToName(url);
5433
+ const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
5434
+ // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5435
+ return join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
5436
+ }
5437
+ /**
5438
+ * Tries to create the cache directory needed for a downloaded knowledge source.
5439
+ *
5440
+ * @private internal utility of `makeKnowledgeSourceHandler`
5441
+ */
5442
+ async function createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose) {
5443
+ try {
5444
+ await tools.fs.mkdir(dirname(fullFilepath), { recursive: true });
5445
+ }
5446
+ catch (error) {
5447
+ if (isVerbose) {
5448
+ console.info(`📄 [3] "${name}" error creating cache directory`);
4796
5449
  }
4797
- if (rootDirname === null) {
4798
- throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
4799
- // <- TODO: [🧠] What is the best error type here`
5450
+ // Note: If we can't create cache directory, we'll handle it when trying to write the file
5451
+ // This handles read-only filesystems, permission issues, and missing parent directories
5452
+ if (!isIgnorableCacheFilesystemError(error)) {
5453
+ throw error;
4800
5454
  }
4801
- const filename = isAbsolute(knowledgeSourceContent)
4802
- ? knowledgeSourceContent
4803
- : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5455
+ }
5456
+ }
5457
+ /**
5458
+ * Tries to write downloaded content into cache and reports whether the cache was usable.
5459
+ *
5460
+ * @private internal utility of `makeKnowledgeSourceHandler`
5461
+ */
5462
+ async function writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose) {
5463
+ // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
5464
+ try {
5465
+ await tools.fs.writeFile(fullFilepath, fileContent);
5466
+ return true;
5467
+ }
5468
+ catch (error) {
4804
5469
  if (isVerbose) {
4805
- console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5470
+ console.info(`📄 [4] "${name}" error writing cache file`);
4806
5471
  }
4807
- const fileExtension = getFileExtension(filename);
4808
- const mimeType = extensionToMimeType(fileExtension || '');
4809
- if (!(await isFileExisting(filename, tools.fs))) {
4810
- throw new NotFoundError(spaceTrim$1((block) => `
4811
- Can not make source handler for file which does not exist:
5472
+ // Note: If we can't write to cache, we'll process the file directly from memory
5473
+ // This handles read-only filesystems like Vercel
5474
+ if (isIgnorableCacheFilesystemError(error)) {
5475
+ return false;
5476
+ }
5477
+ throw error;
5478
+ }
5479
+ }
5480
+ /**
5481
+ * Detects filesystem errors that should not fail optional caching.
5482
+ *
5483
+ * @private internal utility of `makeKnowledgeSourceHandler`
5484
+ */
5485
+ function isIgnorableCacheFilesystemError(error) {
5486
+ return (error instanceof Error &&
5487
+ (error.message.includes('EROFS') ||
5488
+ error.message.includes('read-only') ||
5489
+ error.message.includes('EACCES') ||
5490
+ error.message.includes('EPERM') ||
5491
+ error.message.includes('ENOENT')));
5492
+ }
5493
+ /**
5494
+ * Creates a source handler backed by already downloaded file content kept in memory.
5495
+ *
5496
+ * @private internal utility of `makeKnowledgeSourceHandler`
5497
+ */
5498
+ function makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent) {
5499
+ return {
5500
+ source: name,
5501
+ filename: null,
5502
+ url,
5503
+ mimeType,
5504
+ async asJson() {
5505
+ return JSON.parse(fileContent.toString('utf-8'));
5506
+ },
5507
+ async asText() {
5508
+ return fileContent.toString('utf-8');
5509
+ },
5510
+ };
5511
+ }
5512
+ /**
5513
+ * Creates a source handler for file-based knowledge.
5514
+ *
5515
+ * @private internal utility of `makeKnowledgeSourceHandler`
5516
+ */
5517
+ async function makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose) {
5518
+ if (tools.fs === undefined) {
5519
+ throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
5520
+ // <- TODO: [🧠] What is the best error type here`
5521
+ }
5522
+ if (rootDirname === null) {
5523
+ throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
5524
+ // <- TODO: [🧠] What is the best error type here`
5525
+ }
5526
+ const filename = isAbsolute(knowledgeSourceContent)
5527
+ ? knowledgeSourceContent
5528
+ : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5529
+ if (isVerbose) {
5530
+ console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5531
+ }
5532
+ const fileExtension = getFileExtension(filename);
5533
+ const mimeType = extensionToMimeType(fileExtension || '');
5534
+ if (!(await isFileExisting(filename, tools.fs))) {
5535
+ throw new NotFoundError(spaceTrim$1((block) => `
5536
+ Can not make source handler for file which does not exist:
4812
5537
 
4813
- File:
4814
- ${block(knowledgeSourceContent)}
5538
+ File:
5539
+ ${block(knowledgeSourceContent)}
4815
5540
 
4816
- Full file path:
4817
- ${block(filename)}
4818
- `));
4819
- }
4820
- // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
4821
- return {
4822
- source: name,
4823
- filename,
4824
- url: null,
4825
- mimeType,
4826
- /*
4827
- TODO: [🥽]
4828
- > async asBlob() {
4829
- > const content = await tools.fs!.readFile(filename);
4830
- > return new Blob(
4831
- > [
4832
- > content,
4833
- > // <- TODO: [🥽] This is NOT tested, test it
4834
- > ],
4835
- > { type: mimeType },
4836
- > );
4837
- > },
4838
- */
4839
- async asJson() {
4840
- return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
4841
- },
4842
- async asText() {
4843
- return await tools.fs.readFile(filename, 'utf-8');
4844
- },
4845
- };
5541
+ Full file path:
5542
+ ${block(filename)}
5543
+ `));
4846
5544
  }
4847
- else {
4848
- if (isVerbose) {
4849
- console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
4850
- console.info('---');
4851
- console.info(knowledgeSourceContent);
4852
- console.info('---');
4853
- }
4854
- return {
4855
- source: name,
4856
- filename: null,
4857
- url: null,
4858
- mimeType: 'text/markdown',
4859
- asText() {
4860
- return knowledgeSource.knowledgeSourceContent;
4861
- },
4862
- asJson() {
4863
- throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
4864
- },
4865
- /*
4866
- TODO: [🥽]
4867
- > asBlob() {
4868
- > throw new UnexpectedError(
4869
- > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
4870
- > );
4871
- > },
4872
- */
4873
- };
5545
+ // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
5546
+ return {
5547
+ source: name,
5548
+ filename,
5549
+ url: null,
5550
+ mimeType,
5551
+ /*
5552
+ TODO: [🥽]
5553
+ > async asBlob() {
5554
+ > const content = await tools.fs!.readFile(filename);
5555
+ > return new Blob(
5556
+ > [
5557
+ > content,
5558
+ > // <- TODO: [🥽] This is NOT tested, test it
5559
+ > ],
5560
+ > { type: mimeType },
5561
+ > );
5562
+ > },
5563
+ */
5564
+ async asJson() {
5565
+ return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
5566
+ },
5567
+ async asText() {
5568
+ return await tools.fs.readFile(filename, 'utf-8');
5569
+ },
5570
+ };
5571
+ }
5572
+ /**
5573
+ * Creates a source handler for inline text knowledge.
5574
+ *
5575
+ * @private internal utility of `makeKnowledgeSourceHandler`
5576
+ */
5577
+ function makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose) {
5578
+ if (isVerbose) {
5579
+ console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
5580
+ console.info('---');
5581
+ console.info(knowledgeSourceContent);
5582
+ console.info('---');
4874
5583
  }
5584
+ return {
5585
+ source: name,
5586
+ filename: null,
5587
+ url: null,
5588
+ mimeType: 'text/markdown',
5589
+ asText() {
5590
+ return knowledgeSourceContent;
5591
+ },
5592
+ asJson() {
5593
+ throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
5594
+ },
5595
+ /*
5596
+ TODO: [🥽]
5597
+ > asBlob() {
5598
+ > throw new UnexpectedError(
5599
+ > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
5600
+ > );
5601
+ > },
5602
+ */
5603
+ };
4875
5604
  }
4876
5605
 
4877
5606
  /**
@@ -6064,204 +6793,102 @@ const CountUtils = {
6064
6793
  // TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
6065
6794
  // Note: [💞] Ignore a discrepancy between file name and entity name
6066
6795
 
6067
- /**
6068
- * Function checkExpectations will check if the expectations on given value are met
6069
- *
6070
- * Note: There are two similar functions:
6071
- * - `checkExpectations` which throws an error if the expectations are not met
6072
- * - `isPassingExpectations` which returns a boolean
6073
- *
6074
- * @throws {ExpectError} if the expectations are not met
6075
- * @returns {void} Nothing
6076
- *
6077
- * @private internal function of `createPipelineExecutor`
6078
- */
6079
- function checkExpectations(expectations, value) {
6080
- for (const [unit, { max, min }] of Object.entries(expectations)) {
6081
- const amount = CountUtils[unit.toUpperCase()](value);
6082
- if (min && amount < min) {
6083
- throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6084
- } /* not else */
6085
- if (max && amount > max) {
6086
- throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6087
- }
6088
- }
6089
- }
6090
- // TODO: [💝] Unite object for expecting amount and format
6091
- // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6092
- // Note: [💝] and [🤠] are interconnected together
6093
-
6094
- /**
6095
- * Validates a prompt result against expectations and format requirements.
6096
- * This function provides a common abstraction for result validation that can be used
6097
- * by both execution logic and caching logic to ensure consistency.
6098
- *
6099
- * Note: [🔂] This function is idempotent.
6100
- *
6101
- * @param options - The validation options including result string, expectations, and format
6102
- * @returns Validation result with processed string and validity status
6103
- *
6104
- * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6105
- */
6106
- function validatePromptResult(options) {
6107
- const { resultString, expectations, format } = options;
6108
- let processedResultString = resultString;
6109
- let validationError;
6110
- try {
6111
- // TODO: [💝] Unite object for expecting amount and format
6112
- if (format) {
6113
- if (format === 'JSON') {
6114
- if (!isValidJsonString(processedResultString)) {
6115
- // TODO: [🏢] Do more universally via `FormatParser`
6116
- try {
6117
- processedResultString = extractJsonBlock(processedResultString);
6118
- }
6119
- catch (error) {
6120
- keepUnused(error);
6121
- throw new ExpectError(spaceTrim$1((block) => `
6122
- Expected valid JSON string
6123
-
6124
- The expected JSON text:
6125
- ${block(processedResultString)}
6126
- `));
6127
- }
6128
- }
6129
- }
6130
- else {
6131
- throw new UnexpectedError(`Unknown format "${format}"`);
6132
- }
6133
- }
6134
- // TODO: [💝] Unite object for expecting amount and format
6135
- if (expectations) {
6136
- checkExpectations(expectations, processedResultString);
6137
- }
6138
- return {
6139
- isValid: true,
6140
- processedResultString,
6141
- };
6142
- }
6143
- catch (error) {
6144
- if (error instanceof ExpectError) {
6145
- validationError = error;
6146
- }
6147
- else {
6148
- // Re-throw non-ExpectError errors (like UnexpectedError)
6149
- throw error;
6150
- }
6151
- return {
6152
- isValid: false,
6153
- processedResultString,
6154
- error: validationError,
6155
- };
6156
- }
6157
- }
6158
-
6159
- /**
6160
- * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
6161
- * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
6162
- * Throws errors if execution fails after all attempts.
6163
- *
6164
- * @param options - The options for execution, including task, parameters, pipeline, and configuration.
6165
- * @returns The result string of the executed task.
6166
- *
6167
- * @private internal utility of `createPipelineExecutor`
6168
- */
6169
- async function executeAttempts(options) {
6170
- const $ongoingTaskResult = createOngoingTaskResult();
6171
- const llmTools = getSingleLlmExecutionTools(options.tools.llm);
6172
- attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
6173
- const attempt = createAttemptDescriptor({
6174
- attemptIndex,
6175
- jokerParameterNames: options.jokerParameterNames,
6176
- pipelineIdentification: options.pipelineIdentification,
6177
- });
6178
- resetAttemptExecutionState($ongoingTaskResult);
6179
- try {
6180
- await executeSingleAttempt({
6181
- attempt,
6182
- options,
6183
- llmTools,
6184
- $ongoingTaskResult,
6185
- });
6186
- break attempts;
6187
- }
6188
- catch (error) {
6189
- if (!(error instanceof ExpectError)) {
6190
- throw error;
6191
- }
6192
- recordFailedAttempt({
6193
- error,
6194
- attemptIndex,
6195
- onProgress: options.onProgress,
6196
- $ongoingTaskResult,
6197
- });
6198
- }
6199
- finally {
6200
- reportPromptExecution({
6201
- attempt,
6202
- task: options.task,
6203
- $executionReport: options.$executionReport,
6204
- logLlmCall: options.logLlmCall,
6205
- $ongoingTaskResult,
6206
- });
6207
- }
6208
- throwIfFinalAttemptFailed({
6209
- attemptIndex,
6210
- maxAttempts: options.maxAttempts,
6211
- maxExecutionAttempts: options.maxExecutionAttempts,
6212
- pipelineIdentification: options.pipelineIdentification,
6213
- $ongoingTaskResult,
6214
- });
6215
- }
6216
- return getSuccessfulResultString({
6217
- pipelineIdentification: options.pipelineIdentification,
6218
- $ongoingTaskResult,
6219
- });
6220
- }
6221
- /**
6222
- * Creates mutable attempt state for one task execution lifecycle.
6223
- */
6224
- function createOngoingTaskResult() {
6225
- return {
6226
- $result: null,
6227
- $resultString: null,
6228
- $expectError: null,
6229
- $scriptPipelineExecutionErrors: [],
6230
- $failedResults: [],
6231
- };
6232
- }
6233
- /**
6234
- * Resolves the bookkeeping for one loop iteration, including joker lookup.
6235
- */
6236
- function createAttemptDescriptor(options) {
6237
- const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
6238
- const isJokerAttempt = attemptIndex < 0;
6239
- const jokerParameterName = isJokerAttempt
6240
- ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
6241
- : undefined;
6242
- if (isJokerAttempt && !jokerParameterName) {
6243
- throw new UnexpectedError(spaceTrim$1((block) => `
6244
- Joker not found in attempt ${attemptIndex}
6245
-
6246
- ${block(pipelineIdentification)}
6247
- `));
6796
+ /**
6797
+ * Function checkExpectations will check if the expectations on given value are met
6798
+ *
6799
+ * Note: There are two similar functions:
6800
+ * - `checkExpectations` which throws an error if the expectations are not met
6801
+ * - `isPassingExpectations` which returns a boolean
6802
+ *
6803
+ * @throws {ExpectError} if the expectations are not met
6804
+ * @returns {void} Nothing
6805
+ *
6806
+ * @private internal function of `createPipelineExecutor`
6807
+ */
6808
+ function checkExpectations(expectations, value) {
6809
+ for (const [unit, { max, min }] of Object.entries(expectations)) {
6810
+ const amount = CountUtils[unit.toUpperCase()](value);
6811
+ if (min && amount < min) {
6812
+ throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6813
+ } /* not else */
6814
+ if (max && amount > max) {
6815
+ throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6816
+ }
6248
6817
  }
6249
- return {
6250
- attemptIndex,
6251
- isJokerAttempt,
6252
- jokerParameterName,
6253
- };
6254
6818
  }
6819
+ // TODO: [💝] Unite object for expecting amount and format
6820
+ // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6821
+ // Note: [💝] and [🤠] are interconnected together
6822
+
6255
6823
  /**
6256
- * Clears the per-attempt result slots while preserving cumulative failure history.
6824
+ * Validates a prompt result against expectations and format requirements.
6825
+ * This function provides a common abstraction for result validation that can be used
6826
+ * by both execution logic and caching logic to ensure consistency.
6827
+ *
6828
+ * Note: [🔂] This function is idempotent.
6829
+ *
6830
+ * @param options - The validation options including result string, expectations, and format
6831
+ * @returns Validation result with processed string and validity status
6832
+ *
6833
+ * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6257
6834
  */
6258
- function resetAttemptExecutionState($ongoingTaskResult) {
6259
- $ongoingTaskResult.$result = null;
6260
- $ongoingTaskResult.$resultString = null;
6261
- $ongoingTaskResult.$expectError = null;
6835
+ function validatePromptResult(options) {
6836
+ const { resultString, expectations, format } = options;
6837
+ let processedResultString = resultString;
6838
+ let validationError;
6839
+ try {
6840
+ // TODO: [💝] Unite object for expecting amount and format
6841
+ if (format) {
6842
+ if (format === 'JSON') {
6843
+ if (!isValidJsonString(processedResultString)) {
6844
+ // TODO: [🏢] Do more universally via `FormatParser`
6845
+ try {
6846
+ processedResultString = extractJsonBlock(processedResultString);
6847
+ }
6848
+ catch (error) {
6849
+ keepUnused(error);
6850
+ throw new ExpectError(spaceTrim$1((block) => `
6851
+ Expected valid JSON string
6852
+
6853
+ The expected JSON text:
6854
+ ${block(processedResultString)}
6855
+ `));
6856
+ }
6857
+ }
6858
+ }
6859
+ else {
6860
+ throw new UnexpectedError(`Unknown format "${format}"`);
6861
+ }
6862
+ }
6863
+ // TODO: [💝] Unite object for expecting amount and format
6864
+ if (expectations) {
6865
+ checkExpectations(expectations, processedResultString);
6866
+ }
6867
+ return {
6868
+ isValid: true,
6869
+ processedResultString,
6870
+ };
6871
+ }
6872
+ catch (error) {
6873
+ if (error instanceof ExpectError) {
6874
+ validationError = error;
6875
+ }
6876
+ else {
6877
+ // Re-throw non-ExpectError errors (like UnexpectedError)
6878
+ throw error;
6879
+ }
6880
+ return {
6881
+ isValid: false,
6882
+ processedResultString,
6883
+ error: validationError,
6884
+ };
6885
+ }
6262
6886
  }
6887
+
6263
6888
  /**
6264
6889
  * Executes one loop iteration, from joker resolution or task execution through validation.
6890
+ *
6891
+ * @private function of `executeAttempts`
6265
6892
  */
6266
6893
  async function executeSingleAttempt(options) {
6267
6894
  const { attempt, options: executeAttemptsOptions, llmTools, $ongoingTaskResult } = options;
@@ -6576,11 +7203,15 @@ function validateAttemptResult(options) {
6576
7203
  // Update the result string in case format processing modified it (e.g., JSON extraction)
6577
7204
  $ongoingTaskResult.$resultString = validationResult.processedResultString;
6578
7205
  }
7206
+
6579
7207
  /**
6580
- * Stores one failed attempt and reports the expectation error upstream.
7208
+ * Stores one failed attempt, reports the expectation error, and throws the aggregated retry error after the final
7209
+ * regular attempt.
7210
+ *
7211
+ * @private function of `executeAttempts`
6581
7212
  */
6582
- function recordFailedAttempt(options) {
6583
- const { error, attemptIndex, onProgress, $ongoingTaskResult } = options;
7213
+ function handleAttemptFailure(options) {
7214
+ const { error, attemptIndex, maxAttempts, maxExecutionAttempts, onProgress, pipelineIdentification, $ongoingTaskResult, } = options;
6584
7215
  $ongoingTaskResult.$expectError = error;
6585
7216
  $ongoingTaskResult.$failedResults.push({
6586
7217
  attemptIndex,
@@ -6590,39 +7221,7 @@ function recordFailedAttempt(options) {
6590
7221
  onProgress({
6591
7222
  errors: [error],
6592
7223
  });
6593
- }
6594
- /**
6595
- * Appends the prompt execution report for prompt-task attempts.
6596
- */
6597
- function reportPromptExecution(options) {
6598
- const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
6599
- if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
6600
- return;
6601
- }
6602
- // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
6603
- // In that case we don’t want to make a report about it because it’s not a llm execution error
6604
- const executionPromptReport = {
6605
- prompt: {
6606
- ...$ongoingTaskResult.$prompt,
6607
- // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
6608
- },
6609
- result: $ongoingTaskResult.$result || undefined,
6610
- error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
6611
- };
6612
- $executionReport.promptExecutions.push(executionPromptReport);
6613
- if (logLlmCall) {
6614
- logLlmCall({
6615
- modelName: 'model' /* <- TODO: How to get model name from the report */,
6616
- report: executionPromptReport,
6617
- });
6618
- }
6619
- }
6620
- /**
6621
- * Throws the aggregated retry error after the last regular attempt fails expectations.
6622
- */
6623
- function throwIfFinalAttemptFailed(options) {
6624
- const { attemptIndex, maxAttempts, maxExecutionAttempts, pipelineIdentification, $ongoingTaskResult } = options;
6625
- if ($ongoingTaskResult.$expectError === null || attemptIndex !== maxAttempts - 1) {
7224
+ if (attemptIndex !== maxAttempts - 1) {
6626
7225
  return;
6627
7226
  }
6628
7227
  throw new PipelineExecutionError(spaceTrim$1((block) => {
@@ -6667,6 +7266,136 @@ function quoteMultilineText(text) {
6667
7266
  .map((line) => `> ${line}`)
6668
7267
  .join('\n');
6669
7268
  }
7269
+
7270
+ /**
7271
+ * Appends the prompt execution report for prompt-task attempts.
7272
+ *
7273
+ * @private function of `executeAttempts`
7274
+ */
7275
+ function reportPromptExecution(options) {
7276
+ const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
7277
+ if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
7278
+ return;
7279
+ }
7280
+ // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
7281
+ // In that case we don’t want to make a report about it because it’s not a llm execution error
7282
+ const executionPromptReport = {
7283
+ prompt: {
7284
+ ...$ongoingTaskResult.$prompt,
7285
+ // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
7286
+ },
7287
+ result: $ongoingTaskResult.$result || undefined,
7288
+ error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
7289
+ };
7290
+ $executionReport.promptExecutions.push(executionPromptReport);
7291
+ if (logLlmCall) {
7292
+ logLlmCall({
7293
+ modelName: 'model' /* <- TODO: How to get model name from the report */,
7294
+ report: executionPromptReport,
7295
+ });
7296
+ }
7297
+ }
7298
+
7299
+ /**
7300
+ * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
7301
+ * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
7302
+ * Throws errors if execution fails after all attempts.
7303
+ *
7304
+ * @param options - The options for execution, including task, parameters, pipeline, and configuration.
7305
+ * @returns The result string of the executed task.
7306
+ *
7307
+ * @private internal utility of `createPipelineExecutor`
7308
+ */
7309
+ async function executeAttempts(options) {
7310
+ const $ongoingTaskResult = createOngoingTaskResult();
7311
+ const llmTools = getSingleLlmExecutionTools(options.tools.llm);
7312
+ attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
7313
+ const attempt = createAttemptDescriptor({
7314
+ attemptIndex,
7315
+ jokerParameterNames: options.jokerParameterNames,
7316
+ pipelineIdentification: options.pipelineIdentification,
7317
+ });
7318
+ resetAttemptExecutionState($ongoingTaskResult);
7319
+ try {
7320
+ await executeSingleAttempt({
7321
+ attempt,
7322
+ options,
7323
+ llmTools,
7324
+ $ongoingTaskResult,
7325
+ });
7326
+ break attempts;
7327
+ }
7328
+ catch (error) {
7329
+ if (!(error instanceof ExpectError)) {
7330
+ throw error;
7331
+ }
7332
+ handleAttemptFailure({
7333
+ error,
7334
+ attemptIndex,
7335
+ maxAttempts: options.maxAttempts,
7336
+ maxExecutionAttempts: options.maxExecutionAttempts,
7337
+ onProgress: options.onProgress,
7338
+ pipelineIdentification: options.pipelineIdentification,
7339
+ $ongoingTaskResult,
7340
+ });
7341
+ }
7342
+ finally {
7343
+ reportPromptExecution({
7344
+ attempt,
7345
+ task: options.task,
7346
+ $executionReport: options.$executionReport,
7347
+ logLlmCall: options.logLlmCall,
7348
+ $ongoingTaskResult,
7349
+ });
7350
+ }
7351
+ }
7352
+ return getSuccessfulResultString({
7353
+ pipelineIdentification: options.pipelineIdentification,
7354
+ $ongoingTaskResult,
7355
+ });
7356
+ }
7357
+ /**
7358
+ * Creates mutable attempt state for one task execution lifecycle.
7359
+ */
7360
+ function createOngoingTaskResult() {
7361
+ return {
7362
+ $result: null,
7363
+ $resultString: null,
7364
+ $expectError: null,
7365
+ $scriptPipelineExecutionErrors: [],
7366
+ $failedResults: [],
7367
+ };
7368
+ }
7369
+ /**
7370
+ * Resolves the bookkeeping for one loop iteration, including joker lookup.
7371
+ */
7372
+ function createAttemptDescriptor(options) {
7373
+ const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
7374
+ const isJokerAttempt = attemptIndex < 0;
7375
+ const jokerParameterName = isJokerAttempt
7376
+ ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
7377
+ : undefined;
7378
+ if (isJokerAttempt && !jokerParameterName) {
7379
+ throw new UnexpectedError(spaceTrim$1((block) => `
7380
+ Joker not found in attempt ${attemptIndex}
7381
+
7382
+ ${block(pipelineIdentification)}
7383
+ `));
7384
+ }
7385
+ return {
7386
+ attemptIndex,
7387
+ isJokerAttempt,
7388
+ jokerParameterName,
7389
+ };
7390
+ }
7391
+ /**
7392
+ * Clears the per-attempt result slots while preserving cumulative failure history.
7393
+ */
7394
+ function resetAttemptExecutionState($ongoingTaskResult) {
7395
+ $ongoingTaskResult.$result = null;
7396
+ $ongoingTaskResult.$resultString = null;
7397
+ $ongoingTaskResult.$expectError = null;
7398
+ }
6670
7399
  /**
6671
7400
  * Returns the successful result string or raises an unexpected internal-state error.
6672
7401
  */