@promptbook/documents 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 +1784 -1055
  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 +1784 -1055
  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
@@ -26,7 +26,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
26
26
  * @generated
27
27
  * @see https://github.com/webgptorg/promptbook
28
28
  */
29
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
29
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
30
30
  /**
31
31
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
32
32
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -294,6 +294,111 @@ function checkChannelValue(channelName, value) {
294
294
  }
295
295
  }
296
296
 
297
+ /**
298
+ * Shared immutable channel storage and serialization helpers for `Color`.
299
+ *
300
+ * @private base class of Color
301
+ */
302
+ class ColorValue {
303
+ constructor(red, green, blue, alpha = 255) {
304
+ this.red = red;
305
+ this.green = green;
306
+ this.blue = blue;
307
+ this.alpha = alpha;
308
+ checkChannelValue('Red', red);
309
+ checkChannelValue('Green', green);
310
+ checkChannelValue('Blue', blue);
311
+ checkChannelValue('Alpha', alpha);
312
+ }
313
+ /**
314
+ * Shortcut for `red` property
315
+ * Number from 0 to 255
316
+ * @alias red
317
+ */
318
+ get r() {
319
+ return this.red;
320
+ }
321
+ /**
322
+ * Shortcut for `green` property
323
+ * Number from 0 to 255
324
+ * @alias green
325
+ */
326
+ get g() {
327
+ return this.green;
328
+ }
329
+ /**
330
+ * Shortcut for `blue` property
331
+ * Number from 0 to 255
332
+ * @alias blue
333
+ */
334
+ get b() {
335
+ return this.blue;
336
+ }
337
+ /**
338
+ * Shortcut for `alpha` property
339
+ * Number from 0 (transparent) to 255 (opaque)
340
+ * @alias alpha
341
+ */
342
+ get a() {
343
+ return this.alpha;
344
+ }
345
+ /**
346
+ * Shortcut for `alpha` property
347
+ * Number from 0 (transparent) to 255 (opaque)
348
+ * @alias alpha
349
+ */
350
+ get opacity() {
351
+ return this.alpha;
352
+ }
353
+ /**
354
+ * Shortcut for 1-`alpha` property
355
+ */
356
+ get transparency() {
357
+ return 255 - this.alpha;
358
+ }
359
+ clone() {
360
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
361
+ }
362
+ toString() {
363
+ return this.toHex();
364
+ }
365
+ toHex() {
366
+ if (this.alpha === 255) {
367
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
368
+ .toString(16)
369
+ .padStart(2, '0')}`;
370
+ }
371
+ else {
372
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
373
+ .toString(16)
374
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
375
+ }
376
+ }
377
+ toRgb() {
378
+ if (this.alpha === 255) {
379
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
380
+ }
381
+ else {
382
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
383
+ }
384
+ }
385
+ toHsl() {
386
+ throw new Error(`Getting HSL is not implemented`);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Checks if the given value is a valid hex color string
392
+ *
393
+ * @param value - value to check
394
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
395
+ *
396
+ * @private function of Color
397
+ */
398
+ function isHexColorString(value) {
399
+ 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));
400
+ }
401
+
297
402
  /**
298
403
  * Constant for short hex lengths.
299
404
  */
@@ -505,16 +610,53 @@ function parseAlphaValue(value) {
505
610
 
506
611
  /**
507
612
  * Pattern matching hsl regex.
613
+ *
614
+ * @private function of Color
508
615
  */
509
616
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
510
617
  /**
511
618
  * Pattern matching RGB regex.
619
+ *
620
+ * @private function of Color
512
621
  */
513
622
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
514
623
  /**
515
624
  * Pattern matching rgba regex.
625
+ *
626
+ * @private function of Color
516
627
  */
517
628
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
629
+ /**
630
+ * Parses a supported color string into RGBA channels.
631
+ *
632
+ * @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`,...
633
+ * @returns RGBA channel values.
634
+ *
635
+ * @private function of Color
636
+ */
637
+ function parseColorString(color) {
638
+ const trimmed = color.trim();
639
+ const cssColor = CSS_COLORS[trimmed];
640
+ if (cssColor) {
641
+ return parseColorString(cssColor);
642
+ }
643
+ else if (isHexColorString(trimmed)) {
644
+ return parseHexColor(trimmed);
645
+ }
646
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
647
+ return parseHslColor(trimmed);
648
+ }
649
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
650
+ return parseRgbColor(trimmed);
651
+ }
652
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
653
+ return parseRgbaColor(trimmed);
654
+ }
655
+ else {
656
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
657
+ }
658
+ }
659
+
518
660
  /**
519
661
  * Color object represents an RGB color with alpha channel
520
662
  *
@@ -522,7 +664,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
522
664
  *
523
665
  * @public exported from `@promptbook/color`
524
666
  */
525
- class Color {
667
+ class Color extends ColorValue {
526
668
  /**
527
669
  * Creates a new Color instance from miscellaneous formats
528
670
  * - It can receive Color instance and just return the same instance
@@ -595,25 +737,7 @@ class Color {
595
737
  * @returns Color object
596
738
  */
597
739
  static fromString(color) {
598
- const trimmed = color.trim();
599
- if (CSS_COLORS[trimmed]) {
600
- return Color.fromString(CSS_COLORS[trimmed]);
601
- }
602
- else if (Color.isHexColorString(trimmed)) {
603
- return Color.fromHex(trimmed);
604
- }
605
- if (HSL_REGEX_PATTERN.test(trimmed)) {
606
- return Color.fromHsl(trimmed);
607
- }
608
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
609
- return Color.fromRgbString(trimmed);
610
- }
611
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
612
- return Color.fromRgbaString(trimmed);
613
- }
614
- else {
615
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
616
- }
740
+ return Color.fromColorChannels(parseColorString(color));
617
741
  }
618
742
  /**
619
743
  * Gets common color
@@ -643,8 +767,7 @@ class Color {
643
767
  * @returns Color object
644
768
  */
645
769
  static fromHex(hex) {
646
- const { red, green, blue, alpha } = parseHexColor(hex);
647
- return take(new Color(red, green, blue, alpha));
770
+ return Color.fromColorChannels(parseHexColor(hex));
648
771
  }
649
772
  /**
650
773
  * Creates a new Color instance from color in hsl format
@@ -653,8 +776,7 @@ class Color {
653
776
  * @returns Color object
654
777
  */
655
778
  static fromHsl(hsl) {
656
- const { red, green, blue, alpha } = parseHslColor(hsl);
657
- return take(new Color(red, green, blue, alpha));
779
+ return Color.fromColorChannels(parseHslColor(hsl));
658
780
  }
659
781
  /**
660
782
  * Creates a new Color instance from color in rgb format
@@ -663,8 +785,7 @@ class Color {
663
785
  * @returns Color object
664
786
  */
665
787
  static fromRgbString(rgb) {
666
- const { red, green, blue, alpha } = parseRgbColor(rgb);
667
- return take(new Color(red, green, blue, alpha));
788
+ return Color.fromColorChannels(parseRgbColor(rgb));
668
789
  }
669
790
  /**
670
791
  * Creates a new Color instance from color in rbga format
@@ -673,8 +794,7 @@ class Color {
673
794
  * @returns Color object
674
795
  */
675
796
  static fromRgbaString(rgba) {
676
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
677
- return take(new Color(red, green, blue, alpha));
797
+ return Color.fromColorChannels(parseRgbaColor(rgba));
678
798
  }
679
799
  /**
680
800
  * Creates a new Color for color channels values
@@ -686,7 +806,7 @@ class Color {
686
806
  * @returns Color object
687
807
  */
688
808
  static fromValues(red, green, blue, alpha = 255) {
689
- return take(new Color(red, green, blue, alpha));
809
+ return Color.fromColorChannels({ red, green, blue, alpha });
690
810
  }
691
811
  /**
692
812
  * Checks if the given value is a valid Color object.
@@ -719,8 +839,7 @@ class Color {
719
839
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
720
840
  */
721
841
  static isHexColorString(value) {
722
- return (typeof value === 'string' &&
723
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
842
+ return isHexColorString(value);
724
843
  }
725
844
  /**
726
845
  * Creates new Color object
@@ -733,89 +852,13 @@ class Color {
733
852
  * @param alpha number from 0 (transparent) to 255 (opaque)
734
853
  */
735
854
  constructor(red, green, blue, alpha = 255) {
736
- this.red = red;
737
- this.green = green;
738
- this.blue = blue;
739
- this.alpha = alpha;
740
- checkChannelValue('Red', red);
741
- checkChannelValue('Green', green);
742
- checkChannelValue('Blue', blue);
743
- checkChannelValue('Alpha', alpha);
744
- }
745
- /**
746
- * Shortcut for `red` property
747
- * Number from 0 to 255
748
- * @alias red
749
- */
750
- get r() {
751
- return this.red;
752
- }
753
- /**
754
- * Shortcut for `green` property
755
- * Number from 0 to 255
756
- * @alias green
757
- */
758
- get g() {
759
- return this.green;
760
- }
761
- /**
762
- * Shortcut for `blue` property
763
- * Number from 0 to 255
764
- * @alias blue
765
- */
766
- get b() {
767
- return this.blue;
768
- }
769
- /**
770
- * Shortcut for `alpha` property
771
- * Number from 0 (transparent) to 255 (opaque)
772
- * @alias alpha
773
- */
774
- get a() {
775
- return this.alpha;
776
- }
777
- /**
778
- * Shortcut for `alpha` property
779
- * Number from 0 (transparent) to 255 (opaque)
780
- * @alias alpha
781
- */
782
- get opacity() {
783
- return this.alpha;
784
- }
785
- /**
786
- * Shortcut for 1-`alpha` property
787
- */
788
- get transparency() {
789
- return 255 - this.alpha;
790
- }
791
- clone() {
792
- return take(new Color(this.red, this.green, this.blue, this.alpha));
793
- }
794
- toString() {
795
- return this.toHex();
796
- }
797
- toHex() {
798
- if (this.alpha === 255) {
799
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
800
- .toString(16)
801
- .padStart(2, '0')}`;
802
- }
803
- else {
804
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
805
- .toString(16)
806
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
807
- }
855
+ super(red, green, blue, alpha);
808
856
  }
809
- toRgb() {
810
- if (this.alpha === 255) {
811
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
812
- }
813
- else {
814
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
815
- }
857
+ createColor(red, green, blue, alpha) {
858
+ return new Color(red, green, blue, alpha);
816
859
  }
817
- toHsl() {
818
- throw new Error(`Getting HSL is not implemented`);
860
+ static fromColorChannels({ red, green, blue, alpha }) {
861
+ return take(new Color(red, green, blue, alpha));
819
862
  }
820
863
  }
821
864
 
@@ -1787,6 +1830,60 @@ function validatePipelineString(pipelineString) {
1787
1830
  }
1788
1831
  // TODO: [🧠][🈴] Where is the best location for this file
1789
1832
 
1833
+ /**
1834
+ * Appends one markdown block to an existing markdown document.
1835
+ *
1836
+ * @private internal utility of `pipelineJsonToString`
1837
+ */
1838
+ function appendMarkdownBlock(pipelineString, markdownBlock) {
1839
+ return spaceTrim$1((block) => `
1840
+ ${block(pipelineString)}
1841
+
1842
+ ${block(markdownBlock)}
1843
+ `);
1844
+ }
1845
+
1846
+ /**
1847
+ * Collects pipeline-level commands in the existing serialization order.
1848
+ *
1849
+ * @private internal utility of `pipelineJsonToString`
1850
+ */
1851
+ function createPipelineCommands(pipelineJson) {
1852
+ const { pipelineUrl, bookVersion, parameters } = pipelineJson;
1853
+ const commands = [];
1854
+ if (pipelineUrl) {
1855
+ commands.push(`PIPELINE URL ${pipelineUrl}`);
1856
+ }
1857
+ if (bookVersion !== `undefined`) {
1858
+ commands.push(`BOOK VERSION ${bookVersion}`);
1859
+ }
1860
+ commands.push(...createParameterCommands(parameters, 'INPUT PARAMETER', ({ isInput }) => isInput));
1861
+ commands.push(...createParameterCommands(parameters, 'OUTPUT PARAMETER', ({ isOutput }) => isOutput));
1862
+ return commands;
1863
+ }
1864
+ /**
1865
+ * Builds one group of parameter commands while preserving the original parameter order.
1866
+ *
1867
+ * @private internal utility of `createPipelineCommands`
1868
+ */
1869
+ function createParameterCommands(parameters, commandPrefix, isIncluded) {
1870
+ return parameters
1871
+ .filter((parameter) => isIncluded(parameter))
1872
+ .map((parameter) => `${commandPrefix} ${parameterJsonToString(parameter)}`);
1873
+ }
1874
+ /**
1875
+ * Converts one parameter JSON declaration to the serialized inline form.
1876
+ *
1877
+ * @private internal utility of `createPipelineCommands`
1878
+ */
1879
+ function parameterJsonToString(parameterJson) {
1880
+ const { name, description } = parameterJson;
1881
+ if (!description) {
1882
+ return `{${name}}`;
1883
+ }
1884
+ return `{${name}} ${description}`;
1885
+ }
1886
+
1790
1887
  /**
1791
1888
  * Prettify the html code
1792
1889
  *
@@ -1801,152 +1898,222 @@ function prettifyMarkdown(content) {
1801
1898
  }
1802
1899
 
1803
1900
  /**
1804
- * Makes first letter of a string uppercase
1805
- *
1806
- * Note: [🔂] This function is idempotent.
1901
+ * Creates the initial markdown heading and description of a pipeline.
1807
1902
  *
1808
- * @public exported from `@promptbook/utils`
1903
+ * @private internal utility of `pipelineJsonToString`
1809
1904
  */
1810
- function capitalize(word) {
1811
- return word.substring(0, 1).toUpperCase() + word.substring(1);
1905
+ function createPipelineIntroduction(pipelineJson) {
1906
+ const { title, description } = pipelineJson;
1907
+ const pipelineIntroduction = spaceTrim$1((block) => `
1908
+ # ${title}
1909
+
1910
+ ${block(description || '')}
1911
+ `);
1912
+ // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1913
+ return prettifyMarkdown(pipelineIntroduction);
1812
1914
  }
1813
1915
 
1814
1916
  /**
1815
- * Converts promptbook in JSON format to string format
1917
+ * Renders commands as markdown bullet items.
1816
1918
  *
1817
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1818
- * @param pipelineJson Promptbook in JSON format (.bookc)
1819
- * @returns Promptbook in string format (.book.md)
1919
+ * @private internal utility of `pipelineJsonToString`
1920
+ */
1921
+ function stringifyCommands(commands) {
1922
+ return commands.map((command) => `- ${command}`).join('\n');
1923
+ }
1924
+
1925
+ /**
1926
+ * Makes first letter of a string uppercase
1820
1927
  *
1821
- * @public exported from `@promptbook/core`
1928
+ * Note: [🔂] This function is idempotent.
1929
+ *
1930
+ * @public exported from `@promptbook/utils`
1822
1931
  */
1823
- function pipelineJsonToString(pipelineJson) {
1824
- const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
1825
- let pipelineString = spaceTrim$1((block) => `
1826
- # ${title}
1932
+ function capitalize(word) {
1933
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
1934
+ }
1827
1935
 
1828
- ${block(description || '')}
1829
- `);
1936
+ /**
1937
+ * Collects all task-specific serialization details.
1938
+ *
1939
+ * @private internal utility of `pipelineJsonToString`
1940
+ */
1941
+ function createTaskSerialization(task) {
1942
+ const taskTypeSerialization = createTaskTypeSerialization(task);
1943
+ return {
1944
+ commands: [
1945
+ ...taskTypeSerialization.commands,
1946
+ ...createJokerCommands(task),
1947
+ ...createPostprocessingCommands(task),
1948
+ ...createExpectationCommands(task),
1949
+ ...createFormatCommands(task),
1950
+ ],
1951
+ contentLanguage: taskTypeSerialization.contentLanguage,
1952
+ };
1953
+ }
1954
+ /**
1955
+ * Collects commands and content language driven by the task type.
1956
+ *
1957
+ * @private internal utility of `createTaskSerialization`
1958
+ */
1959
+ function createTaskTypeSerialization(task) {
1960
+ if (task.taskType === 'PROMPT_TASK') {
1961
+ return {
1962
+ commands: createPromptTaskCommands(task),
1963
+ contentLanguage: 'text',
1964
+ };
1965
+ }
1966
+ if (task.taskType === 'SIMPLE_TASK') {
1967
+ return {
1968
+ commands: ['SIMPLE TEMPLATE'],
1969
+ contentLanguage: 'text',
1970
+ };
1971
+ }
1972
+ if (task.taskType === 'SCRIPT_TASK') {
1973
+ return {
1974
+ commands: ['SCRIPT'],
1975
+ contentLanguage: task.contentLanguage || '',
1976
+ };
1977
+ }
1978
+ if (task.taskType === 'DIALOG_TASK') {
1979
+ return {
1980
+ commands: ['DIALOG'],
1981
+ contentLanguage: 'text',
1982
+ };
1983
+ }
1984
+ return {
1985
+ commands: [],
1986
+ contentLanguage: 'text',
1987
+ };
1988
+ }
1989
+ /**
1990
+ * Collects prompt-task-specific commands.
1991
+ *
1992
+ * @private internal utility of `createTaskSerialization`
1993
+ */
1994
+ function createPromptTaskCommands(task) {
1995
+ const { modelName, modelVariant } = task.modelRequirements || {};
1830
1996
  const commands = [];
1831
- if (pipelineUrl) {
1832
- commands.push(`PIPELINE URL ${pipelineUrl}`);
1997
+ // Note: Do nothing, it is default
1998
+ // commands.push(`PROMPT`);
1999
+ if (modelVariant) {
2000
+ commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1833
2001
  }
1834
- if (bookVersion !== `undefined`) {
1835
- commands.push(`BOOK VERSION ${bookVersion}`);
2002
+ if (modelName) {
2003
+ commands.push(`MODEL NAME \`${modelName}\``);
1836
2004
  }
1837
- // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1838
- pipelineString = prettifyMarkdown(pipelineString);
1839
- for (const parameter of parameters.filter(({ isInput }) => isInput)) {
1840
- commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
2005
+ return commands;
2006
+ }
2007
+ /**
2008
+ * Collects joker commands.
2009
+ *
2010
+ * @private internal utility of `createTaskSerialization`
2011
+ */
2012
+ function createJokerCommands(task) {
2013
+ var _a;
2014
+ return ((_a = task.jokerParameterNames) === null || _a === void 0 ? void 0 : _a.map((joker) => `JOKER {${joker}}`)) || [];
2015
+ }
2016
+ /**
2017
+ * Collects postprocessing commands.
2018
+ *
2019
+ * @private internal utility of `createTaskSerialization`
2020
+ */
2021
+ function createPostprocessingCommands(task) {
2022
+ var _a;
2023
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
2024
+ }
2025
+ /**
2026
+ * Collects expectation commands.
2027
+ *
2028
+ * @private internal utility of `createTaskSerialization`
2029
+ */
2030
+ function createExpectationCommands(task) {
2031
+ if (!task.expectations) {
2032
+ return [];
1841
2033
  }
1842
- for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
1843
- commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
2034
+ return Object.entries(task.expectations).flatMap(([unit, expectation]) => createExpectationCommandsForUnit(unit, expectation.min, expectation.max));
2035
+ }
2036
+ /**
2037
+ * Collects expectation commands for a single unit.
2038
+ *
2039
+ * @private internal utility of `createTaskSerialization`
2040
+ */
2041
+ function createExpectationCommandsForUnit(unit, min, max) {
2042
+ if (min === max) {
2043
+ return [`EXPECT EXACTLY ${min} ${formatExpectationUnit(unit, min)}`];
1844
2044
  }
1845
- pipelineString = spaceTrim$1((block) => `
1846
- ${block(pipelineString)}
1847
-
1848
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
1849
- `);
1850
- for (const task of tasks) {
1851
- const {
1852
- /* Note: Not using:> name, */
1853
- title, description,
1854
- /* Note: dependentParameterNames, */
1855
- jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
1856
- const commands = [];
1857
- let contentLanguage = 'text';
1858
- if (taskType === 'PROMPT_TASK') {
1859
- const { modelRequirements } = task;
1860
- const { modelName, modelVariant } = modelRequirements || {};
1861
- // Note: Do nothing, it is default
1862
- // commands.push(`PROMPT`);
1863
- if (modelVariant) {
1864
- commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1865
- }
1866
- if (modelName) {
1867
- commands.push(`MODEL NAME \`${modelName}\``);
1868
- }
1869
- }
1870
- else if (taskType === 'SIMPLE_TASK') {
1871
- commands.push(`SIMPLE TEMPLATE`);
1872
- // Note: Nothing special here
1873
- }
1874
- else if (taskType === 'SCRIPT_TASK') {
1875
- commands.push(`SCRIPT`);
1876
- if (task.contentLanguage) {
1877
- contentLanguage = task.contentLanguage;
1878
- }
1879
- else {
1880
- contentLanguage = '';
1881
- }
1882
- }
1883
- else if (taskType === 'DIALOG_TASK') {
1884
- commands.push(`DIALOG`);
1885
- // Note: Nothing special here
1886
- } // <- }else if([🅱]
1887
- if (jokers) {
1888
- for (const joker of jokers) {
1889
- commands.push(`JOKER {${joker}}`);
1890
- }
1891
- } /* not else */
1892
- if (postprocessing) {
1893
- for (const postprocessingFunctionName of postprocessing) {
1894
- commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
1895
- }
1896
- } /* not else */
1897
- if (expectations) {
1898
- for (const [unit, { min, max }] of Object.entries(expectations)) {
1899
- if (min === max) {
1900
- commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1901
- }
1902
- else {
1903
- if (min !== undefined) {
1904
- commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1905
- } /* not else */
1906
- if (max !== undefined) {
1907
- commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
1908
- }
1909
- }
1910
- }
1911
- } /* not else */
1912
- if (format) {
1913
- if (format === 'JSON') {
1914
- // TODO: @deprecated remove
1915
- commands.push(`FORMAT JSON`);
1916
- }
1917
- } /* not else */
1918
- pipelineString = spaceTrim$1((block) => `
1919
- ${block(pipelineString)}
2045
+ const commands = [];
2046
+ if (min !== undefined) {
2047
+ commands.push(`EXPECT MIN ${min} ${formatExpectationUnit(unit, min)}`);
2048
+ }
2049
+ if (max !== undefined) {
2050
+ commands.push(`EXPECT MAX ${max} ${formatExpectationUnit(unit, max)}`);
2051
+ }
2052
+ return commands;
2053
+ }
2054
+ /**
2055
+ * Formats the expectation unit exactly as the legacy serializer does.
2056
+ *
2057
+ * @private internal utility of `createTaskSerialization`
2058
+ */
2059
+ function formatExpectationUnit(unit, amount) {
2060
+ return capitalize(unit + (amount > 1 ? 's' : ''));
2061
+ }
2062
+ /**
2063
+ * Collects format commands.
2064
+ *
2065
+ * @private internal utility of `createTaskSerialization`
2066
+ */
2067
+ function createFormatCommands(task) {
2068
+ if (task.format === 'JSON') {
2069
+ // TODO: @deprecated remove
2070
+ return ['FORMAT JSON'];
2071
+ }
2072
+ return [];
2073
+ }
1920
2074
 
1921
- ## ${title}
2075
+ /**
2076
+ * Stringifies one task section of the pipeline.
2077
+ *
2078
+ * @private internal utility of `pipelineJsonToString`
2079
+ */
2080
+ function stringifyTask(task) {
2081
+ const { title, description, content, resultingParameterName } = task;
2082
+ const { commands, contentLanguage } = createTaskSerialization(task);
2083
+ return spaceTrim$1((block) => `
2084
+ ## ${title}
1922
2085
 
1923
- ${block(description || '')}
2086
+ ${block(description || '')}
1924
2087
 
1925
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
2088
+ ${block(stringifyCommands(commands))}
1926
2089
 
1927
- \`\`\`${contentLanguage}
1928
- ${block(spaceTrim$1(content))}
1929
- \`\`\`
2090
+ \`\`\`${contentLanguage}
2091
+ ${block(spaceTrim$1(content))}
2092
+ \`\`\`
1930
2093
 
1931
- \`-> {${resultingParameterName}}\`
1932
- `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1933
- // <- TODO: [main] !!3 Escape
1934
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1935
- }
1936
- return validatePipelineString(pipelineString);
2094
+ \`-> {${resultingParameterName}}\`
2095
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
2096
+ // <- TODO: [main] !!3 Escape
2097
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1937
2098
  }
2099
+
1938
2100
  /**
1939
- * Handles task parameter Json to string.
2101
+ * Converts promptbook in JSON format to string format
1940
2102
  *
1941
- * @private internal utility of `pipelineJsonToString`
2103
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
2104
+ * @param pipelineJson Promptbook in JSON format (.bookc)
2105
+ * @returns Promptbook in string format (.book.md)
2106
+ *
2107
+ * @public exported from `@promptbook/core`
1942
2108
  */
1943
- function taskParameterJsonToString(taskParameterJson) {
1944
- const { name, description } = taskParameterJson;
1945
- let parameterString = `{${name}}`;
1946
- if (description) {
1947
- parameterString = `${parameterString} ${description}`;
2109
+ function pipelineJsonToString(pipelineJson) {
2110
+ let pipelineString = createPipelineIntroduction(pipelineJson);
2111
+ const pipelineCommands = createPipelineCommands(pipelineJson);
2112
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
2113
+ for (const task of pipelineJson.tasks) {
2114
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
1948
2115
  }
1949
- return parameterString;
2116
+ return validatePipelineString(pipelineString);
1950
2117
  }
1951
2118
  // TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
1952
2119
  // TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
@@ -2018,120 +2185,183 @@ function $deepFreeze(objectValue) {
2018
2185
  * @public exported from `@promptbook/utils`
2019
2186
  */
2020
2187
  function checkSerializableAsJson(options) {
2021
- const { value, name, message } = options;
2188
+ checkSerializableValue(options);
2189
+ }
2190
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2191
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2192
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2193
+ /**
2194
+ * Checks one value and dispatches to the appropriate specialized validator.
2195
+ *
2196
+ * @private function of `checkSerializableAsJson`
2197
+ */
2198
+ function checkSerializableValue(options) {
2199
+ const { value } = options;
2200
+ if (isSerializablePrimitive(value)) {
2201
+ return;
2202
+ }
2022
2203
  if (value === undefined) {
2023
- throw new UnexpectedError(`${name} is undefined`);
2204
+ throw new UnexpectedError(`${options.name} is undefined`);
2024
2205
  }
2025
- else if (value === null) {
2026
- return;
2206
+ if (typeof value === 'symbol') {
2207
+ throw new UnexpectedError(`${options.name} is symbol`);
2027
2208
  }
2028
- else if (typeof value === 'boolean') {
2029
- return;
2209
+ if (typeof value === 'function') {
2210
+ throw new UnexpectedError(`${options.name} is function`);
2030
2211
  }
2031
- else if (typeof value === 'number' && !isNaN(value)) {
2212
+ if (Array.isArray(value)) {
2213
+ checkSerializableArray(options, value);
2032
2214
  return;
2033
2215
  }
2034
- else if (typeof value === 'string') {
2216
+ if (value !== null && typeof value === 'object') {
2217
+ checkSerializableObject(options, value);
2035
2218
  return;
2036
2219
  }
2037
- else if (typeof value === 'symbol') {
2038
- throw new UnexpectedError(`${name} is symbol`);
2039
- }
2040
- else if (typeof value === 'function') {
2041
- throw new UnexpectedError(`${name} is function`);
2042
- }
2043
- else if (typeof value === 'object' && Array.isArray(value)) {
2044
- for (let i = 0; i < value.length; i++) {
2045
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
2046
- }
2220
+ throwUnknownTypeError(options);
2221
+ }
2222
+ /**
2223
+ * Checks the primitive values that are directly JSON serializable.
2224
+ *
2225
+ * @private function of `checkSerializableAsJson`
2226
+ */
2227
+ function isSerializablePrimitive(value) {
2228
+ return (value === null ||
2229
+ typeof value === 'boolean' ||
2230
+ (typeof value === 'number' && !isNaN(value)) ||
2231
+ typeof value === 'string');
2232
+ }
2233
+ /**
2234
+ * Recursively checks JSON array items.
2235
+ *
2236
+ * @private function of `checkSerializableAsJson`
2237
+ */
2238
+ function checkSerializableArray(context, arrayValue) {
2239
+ for (let index = 0; index < arrayValue.length; index++) {
2240
+ checkSerializableAsJson({
2241
+ ...context,
2242
+ name: `${context.name}[${index}]`,
2243
+ value: arrayValue[index],
2244
+ });
2047
2245
  }
2048
- else if (typeof value === 'object') {
2049
- if (value instanceof Date) {
2050
- throw new UnexpectedError(spaceTrim$1((block) => `
2051
- \`${name}\` is Date
2246
+ }
2247
+ /**
2248
+ * Checks object-like values and dispatches special unsupported built-ins.
2249
+ *
2250
+ * @private function of `checkSerializableAsJson`
2251
+ */
2252
+ function checkSerializableObject(context, objectValue) {
2253
+ checkUnsupportedObjectType(context, objectValue);
2254
+ checkSerializableObjectEntries(context, objectValue);
2255
+ assertJsonStringificationSucceeds(context, objectValue);
2256
+ }
2257
+ /**
2258
+ * Rejects built-in objects that must be converted before JSON serialization.
2259
+ *
2260
+ * @private function of `checkSerializableAsJson`
2261
+ */
2262
+ function checkUnsupportedObjectType(context, objectValue) {
2263
+ if (objectValue instanceof Date) {
2264
+ throw new UnexpectedError(spaceTrim$1((block) => `
2265
+ \`${context.name}\` is Date
2052
2266
 
2053
- Use \`string_date_iso8601\` instead
2267
+ Use \`string_date_iso8601\` instead
2054
2268
 
2055
- Additional message for \`${name}\`:
2056
- ${block(message || '(nothing)')}
2057
- `));
2058
- }
2059
- else if (value instanceof Map) {
2060
- throw new UnexpectedError(`${name} is Map`);
2061
- }
2062
- else if (value instanceof Set) {
2063
- throw new UnexpectedError(`${name} is Set`);
2064
- }
2065
- else if (value instanceof RegExp) {
2066
- throw new UnexpectedError(`${name} is RegExp`);
2067
- }
2068
- else if (value instanceof Error) {
2069
- throw new UnexpectedError(spaceTrim$1((block) => `
2070
- \`${name}\` is unserialized Error
2269
+ Additional message for \`${context.name}\`:
2270
+ ${block(context.message || '(nothing)')}
2271
+ `));
2272
+ }
2273
+ if (objectValue instanceof Map) {
2274
+ throw new UnexpectedError(`${context.name} is Map`);
2275
+ }
2276
+ if (objectValue instanceof Set) {
2277
+ throw new UnexpectedError(`${context.name} is Set`);
2278
+ }
2279
+ if (objectValue instanceof RegExp) {
2280
+ throw new UnexpectedError(`${context.name} is RegExp`);
2281
+ }
2282
+ if (objectValue instanceof Error) {
2283
+ throw new UnexpectedError(spaceTrim$1((block) => `
2284
+ \`${context.name}\` is unserialized Error
2071
2285
 
2072
- Use function \`serializeError\`
2286
+ Use function \`serializeError\`
2073
2287
 
2074
- Additional message for \`${name}\`:
2075
- ${block(message || '(nothing)')}
2288
+ Additional message for \`${context.name}\`:
2289
+ ${block(context.message || '(nothing)')}
2076
2290
 
2077
- `));
2291
+ `));
2292
+ }
2293
+ }
2294
+ /**
2295
+ * Recursively checks object properties while preserving omitted `undefined` keys.
2296
+ *
2297
+ * @private function of `checkSerializableAsJson`
2298
+ */
2299
+ function checkSerializableObjectEntries(context, objectValue) {
2300
+ for (const [subName, subValue] of Object.entries(objectValue)) {
2301
+ if (subValue === undefined) {
2302
+ // Note: undefined in object is serializable - it is just omitted
2303
+ continue;
2078
2304
  }
2079
- else {
2080
- for (const [subName, subValue] of Object.entries(value)) {
2081
- if (subValue === undefined) {
2082
- // Note: undefined in object is serializable - it is just omitted
2083
- continue;
2084
- }
2085
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
2086
- }
2087
- try {
2088
- JSON.stringify(value); // <- TODO: [0]
2089
- }
2090
- catch (error) {
2091
- assertsError(error);
2092
- throw new UnexpectedError(spaceTrim$1((block) => `
2093
- \`${name}\` is not serializable
2305
+ checkSerializableAsJson({
2306
+ ...context,
2307
+ name: `${context.name}.${subName}`,
2308
+ value: subValue,
2309
+ });
2310
+ }
2311
+ }
2312
+ /**
2313
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
2314
+ *
2315
+ * @private function of `checkSerializableAsJson`
2316
+ */
2317
+ function assertJsonStringificationSucceeds(context, objectValue) {
2318
+ try {
2319
+ JSON.stringify(objectValue); // <- TODO: [0]
2320
+ }
2321
+ catch (error) {
2322
+ assertsError(error);
2323
+ throw new UnexpectedError(spaceTrim$1((block) => `
2324
+ \`${context.name}\` is not serializable
2094
2325
 
2095
- ${block(error.stack || error.message)}
2326
+ ${block(error.stack || error.message)}
2096
2327
 
2097
- Additional message for \`${name}\`:
2098
- ${block(message || '(nothing)')}
2099
- `));
2328
+ Additional message for \`${context.name}\`:
2329
+ ${block(context.message || '(nothing)')}
2330
+ `));
2331
+ }
2332
+ /*
2333
+ TODO: [0] Is there some more elegant way to check circular references?
2334
+ const seen = new Set();
2335
+ const stack = [{ value }];
2336
+ while (stack.length > 0) {
2337
+ const { value } = stack.pop()!;
2338
+ if (typeof value === 'object' && value !== null) {
2339
+ if (seen.has(value)) {
2340
+ throw new UnexpectedError(`${name} has circular reference`);
2100
2341
  }
2101
- /*
2102
- TODO: [0] Is there some more elegant way to check circular references?
2103
- const seen = new Set();
2104
- const stack = [{ value }];
2105
- while (stack.length > 0) {
2106
- const { value } = stack.pop()!;
2107
- if (typeof value === 'object' && value !== null) {
2108
- if (seen.has(value)) {
2109
- throw new UnexpectedError(`${name} has circular reference`);
2110
- }
2111
- seen.add(value);
2112
- if (Array.isArray(value)) {
2113
- stack.push(...value.map((value) => ({ value })));
2114
- } else {
2115
- stack.push(...Object.values(value).map((value) => ({ value })));
2116
- }
2117
- }
2342
+ seen.add(value);
2343
+ if (Array.isArray(value)) {
2344
+ stack.push(...value.map((value) => ({ value })));
2345
+ } else {
2346
+ stack.push(...Object.values(value).map((value) => ({ value })));
2118
2347
  }
2119
- */
2120
- return;
2121
2348
  }
2122
2349
  }
2123
- else {
2124
- throw new UnexpectedError(spaceTrim$1((block) => `
2125
- \`${name}\` is unknown type
2350
+ */
2351
+ }
2352
+ /**
2353
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2354
+ *
2355
+ * @private function of `checkSerializableAsJson`
2356
+ */
2357
+ function throwUnknownTypeError(context) {
2358
+ throw new UnexpectedError(spaceTrim$1((block) => `
2359
+ \`${context.name}\` is unknown type
2126
2360
 
2127
- Additional message for \`${name}\`:
2128
- ${block(message || '(nothing)')}
2129
- `));
2130
- }
2361
+ Additional message for \`${context.name}\`:
2362
+ ${block(context.message || '(nothing)')}
2363
+ `));
2131
2364
  }
2132
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2133
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2134
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2135
2365
 
2136
2366
  /**
2137
2367
  * Creates a deep clone of the given object
@@ -2386,233 +2616,518 @@ function validatePipeline(pipeline) {
2386
2616
  */
2387
2617
  function validatePipeline_InnerFunction(pipeline) {
2388
2618
  // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
2389
- const pipelineIdentification = (() => {
2390
- // Note: This is a 😐 implementation of [🚞]
2391
- const _ = [];
2392
- if (pipeline.sourceFile !== undefined) {
2393
- _.push(`File: ${pipeline.sourceFile}`);
2394
- }
2395
- if (pipeline.pipelineUrl !== undefined) {
2396
- _.push(`Url: ${pipeline.pipelineUrl}`);
2397
- }
2398
- return _.join('\n');
2399
- })();
2400
- if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
2401
- // <- Note: [🚲]
2402
- throw new PipelineLogicError(spaceTrim$1((block) => `
2403
- Invalid promptbook URL "${pipeline.pipelineUrl}"
2404
-
2405
- ${block(pipelineIdentification)}
2406
- `));
2619
+ const context = createPipelineValidationContext(pipeline);
2620
+ validatePipelineMetadata(context);
2621
+ validatePipelineCollectionsStructure(context);
2622
+ validatePipelineParameters(context);
2623
+ validatePipelineTasks(context);
2624
+ validatePipelineDependencyResolution(context);
2625
+ // Note: Check that formfactor is corresponding to the pipeline interface
2626
+ // TODO: !!6 Implement this
2627
+ // pipeline.formfactorName
2628
+ }
2629
+ /**
2630
+ * Creates the shared validation context for one pipeline.
2631
+ *
2632
+ * @private internal utility of `validatePipeline`
2633
+ */
2634
+ function createPipelineValidationContext(pipeline) {
2635
+ return {
2636
+ pipeline,
2637
+ pipelineIdentification: getPipelineIdentification(pipeline),
2638
+ };
2639
+ }
2640
+ /**
2641
+ * Builds a short file/url identification block for validation errors.
2642
+ *
2643
+ * @private internal utility of `validatePipeline`
2644
+ */
2645
+ function getPipelineIdentification(pipeline) {
2646
+ // Note: This is a 😐 implementation of [🚞]
2647
+ const pipelineIdentificationParts = [];
2648
+ if (pipeline.sourceFile !== undefined) {
2649
+ pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
2407
2650
  }
2408
- if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
2409
- // <- Note: [🚲]
2410
- throw new PipelineLogicError(spaceTrim$1((block) => `
2411
- Invalid Promptbook Version "${pipeline.bookVersion}"
2412
-
2413
- ${block(pipelineIdentification)}
2414
- `));
2651
+ if (pipeline.pipelineUrl !== undefined) {
2652
+ pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
2415
2653
  }
2416
- // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2417
- if (!Array.isArray(pipeline.parameters)) {
2418
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2419
- throw new ParseError(spaceTrim$1((block) => `
2420
- Pipeline is valid JSON but with wrong structure
2421
-
2422
- \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2654
+ return pipelineIdentificationParts.join('\n');
2655
+ }
2656
+ /**
2657
+ * Validates pipeline-level metadata fields.
2658
+ *
2659
+ * @private internal step of `validatePipeline`
2660
+ */
2661
+ function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
2662
+ validatePipelineUrl(pipeline, pipelineIdentification);
2663
+ validatePipelineBookVersion(pipeline, pipelineIdentification);
2664
+ }
2665
+ /**
2666
+ * Validates that the expected top-level collections have array structure.
2667
+ *
2668
+ * @private internal step of `validatePipeline`
2669
+ */
2670
+ function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification }) {
2671
+ validatePipelineParametersCollection(pipeline, pipelineIdentification);
2672
+ validatePipelineTasksCollection(pipeline, pipelineIdentification);
2673
+ }
2674
+ /**
2675
+ * Validates all pipeline parameter declarations.
2676
+ *
2677
+ * @private internal step of `validatePipeline`
2678
+ */
2679
+ function validatePipelineParameters({ pipeline, pipelineIdentification }) {
2680
+ for (const parameter of pipeline.parameters) {
2681
+ validatePipelineParameter(parameter, pipeline, pipelineIdentification);
2682
+ }
2683
+ }
2684
+ /**
2685
+ * Validates all pipeline tasks and their per-task invariants.
2686
+ *
2687
+ * @private internal step of `validatePipeline`
2688
+ */
2689
+ function validatePipelineTasks({ pipeline, pipelineIdentification }) {
2690
+ // Note: All input parameters are defined - so that they can be used as result of some task
2691
+ const definedParameters = createInitiallyDefinedParameters(pipeline);
2692
+ for (const task of pipeline.tasks) {
2693
+ validatePipelineTask(task, definedParameters, pipelineIdentification);
2694
+ }
2695
+ }
2696
+ /**
2697
+ * Validates that task dependencies can be resolved without cycles or missing definitions.
2698
+ *
2699
+ * @private internal step of `validatePipeline`
2700
+ */
2701
+ function validatePipelineDependencyResolution({ pipeline, pipelineIdentification }) {
2702
+ let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
2703
+ let loopLimit = LOOP_LIMIT;
2704
+ while (hasUnresolvedTasks(dependencyResolutionState)) {
2705
+ if (loopLimit-- < 0) {
2706
+ throw createDependencyResolutionLoopLimitError(pipelineIdentification);
2707
+ }
2708
+ dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
2709
+ }
2710
+ }
2711
+ /**
2712
+ * Validates one pipeline parameter declaration.
2713
+ *
2714
+ * @private internal step of `validatePipeline`
2715
+ */
2716
+ function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
2717
+ validateParameterDirection(parameter, pipelineIdentification);
2718
+ validateParameterUsage(parameter, pipeline, pipelineIdentification);
2719
+ validateParameterDefinition(parameter, pipeline, pipelineIdentification);
2720
+ }
2721
+ /**
2722
+ * Validates one pipeline task and its invariants.
2723
+ *
2724
+ * @private internal step of `validatePipeline`
2725
+ */
2726
+ function validatePipelineTask(task, definedParameters, pipelineIdentification) {
2727
+ validateTaskResultingParameter(task, definedParameters, pipelineIdentification);
2728
+ validateTaskJokers(task, pipelineIdentification);
2729
+ validateTaskExpectations(task, pipelineIdentification);
2730
+ }
2731
+ /**
2732
+ * Validates the pipeline URL, when present.
2733
+ *
2734
+ * @private internal utility of `validatePipeline`
2735
+ */
2736
+ function validatePipelineUrl(pipeline, pipelineIdentification) {
2737
+ if (pipeline.pipelineUrl === undefined || isValidPipelineUrl(pipeline.pipelineUrl)) {
2738
+ return;
2739
+ }
2740
+ // <- Note: [🚲]
2741
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2742
+ Invalid promptbook URL "${pipeline.pipelineUrl}"
2423
2743
 
2424
- ${block(pipelineIdentification)}
2425
- `));
2744
+ ${block(pipelineIdentification)}
2745
+ `));
2746
+ }
2747
+ /**
2748
+ * Validates the Promptbook version, when present.
2749
+ *
2750
+ * @private internal utility of `validatePipeline`
2751
+ */
2752
+ function validatePipelineBookVersion(pipeline, pipelineIdentification) {
2753
+ if (pipeline.bookVersion === undefined || isValidPromptbookVersion(pipeline.bookVersion)) {
2754
+ return;
2426
2755
  }
2756
+ // <- Note: [🚲]
2757
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2758
+ Invalid Promptbook Version "${pipeline.bookVersion}"
2759
+
2760
+ ${block(pipelineIdentification)}
2761
+ `));
2762
+ }
2763
+ /**
2764
+ * Validates that `pipeline.parameters` is an array.
2765
+ *
2766
+ * @private internal utility of `validatePipeline`
2767
+ */
2768
+ function validatePipelineParametersCollection(pipeline, pipelineIdentification) {
2427
2769
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2428
- if (!Array.isArray(pipeline.tasks)) {
2429
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2430
- throw new ParseError(spaceTrim$1((block) => `
2431
- Pipeline is valid JSON but with wrong structure
2770
+ if (Array.isArray(pipeline.parameters)) {
2771
+ return;
2772
+ }
2773
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2774
+ throw new ParseError(spaceTrim$1((block) => `
2775
+ Pipeline is valid JSON but with wrong structure
2432
2776
 
2433
- \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2777
+ \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2434
2778
 
2435
- ${block(pipelineIdentification)}
2436
- `));
2779
+ ${block(pipelineIdentification)}
2780
+ `));
2781
+ }
2782
+ /**
2783
+ * Validates that `pipeline.tasks` is an array.
2784
+ *
2785
+ * @private internal utility of `validatePipeline`
2786
+ */
2787
+ function validatePipelineTasksCollection(pipeline, pipelineIdentification) {
2788
+ // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2789
+ if (Array.isArray(pipeline.tasks)) {
2790
+ return;
2437
2791
  }
2438
- /*
2439
- TODO: [🧠][🅾] Should be empty pipeline valid or not
2440
- // Note: Check that pipeline has some tasks
2441
- if (pipeline.tasks.length === 0) {
2442
- throw new PipelineLogicError(
2443
- spaceTrim(
2444
- (block) => `
2445
- Pipeline must have at least one task
2792
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2793
+ throw new ParseError(spaceTrim$1((block) => `
2794
+ Pipeline is valid JSON but with wrong structure
2446
2795
 
2447
- ${block(pipelineIdentification)}
2448
- `,
2449
- ),
2450
- );
2796
+ \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2797
+
2798
+ ${block(pipelineIdentification)}
2799
+ `));
2800
+ }
2801
+ /**
2802
+ * Validates that one parameter does not declare incompatible directions.
2803
+ *
2804
+ * @private internal utility of `validatePipeline`
2805
+ */
2806
+ function validateParameterDirection(parameter, pipelineIdentification) {
2807
+ if (!parameter.isInput || !parameter.isOutput) {
2808
+ return;
2451
2809
  }
2452
- */
2453
- // Note: Check each parameter individually
2454
- for (const parameter of pipeline.parameters) {
2455
- if (parameter.isInput && parameter.isOutput) {
2456
- throw new PipelineLogicError(spaceTrim$1((block) => `
2810
+ const parameterName = parameter.name;
2811
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2457
2812
 
2458
- Parameter \`{${parameter.name}}\` can not be both input and output
2813
+ Parameter \`{${parameterName}}\` can not be both input and output
2459
2814
 
2460
- ${block(pipelineIdentification)}
2461
- `));
2462
- }
2463
- // Note: Testing that parameter is either intermediate or output BUT not created and unused
2464
- if (!parameter.isInput &&
2465
- !parameter.isOutput &&
2466
- !pipeline.tasks.some((task) => task.dependentParameterNames.includes(parameter.name))) {
2467
- throw new PipelineLogicError(spaceTrim$1((block) => `
2468
- Parameter \`{${parameter.name}}\` is created but not used
2815
+ ${block(pipelineIdentification)}
2816
+ `));
2817
+ }
2818
+ /**
2819
+ * Validates that one intermediate parameter is actually consumed by at least one task.
2820
+ *
2821
+ * @private internal utility of `validatePipeline`
2822
+ */
2823
+ function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
2824
+ if (parameter.isInput || parameter.isOutput || isParameterUsedByAnyTask(parameter, pipeline.tasks)) {
2825
+ return;
2826
+ }
2827
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2828
+ Parameter \`{${parameter.name}}\` is created but not used
2469
2829
 
2470
- You can declare {${parameter.name}} as output parameter by adding in the header:
2471
- - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2830
+ You can declare {${parameter.name}} as output parameter by adding in the header:
2831
+ - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2472
2832
 
2473
- ${block(pipelineIdentification)}
2833
+ ${block(pipelineIdentification)}
2474
2834
 
2475
- `));
2476
- }
2477
- // Note: Testing that parameter is either input or result of some task
2478
- if (!parameter.isInput && !pipeline.tasks.some((task) => task.resultingParameterName === parameter.name)) {
2479
- throw new PipelineLogicError(spaceTrim$1((block) => `
2480
- Parameter \`{${parameter.name}}\` is declared but not defined
2835
+ `));
2836
+ }
2837
+ /**
2838
+ * Validates that one non-input parameter is produced by at least one task.
2839
+ *
2840
+ * @private internal utility of `validatePipeline`
2841
+ */
2842
+ function validateParameterDefinition(parameter, pipeline, pipelineIdentification) {
2843
+ if (parameter.isInput || isParameterDefinedByAnyTask(parameter, pipeline.tasks)) {
2844
+ return;
2845
+ }
2846
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2847
+ Parameter \`{${parameter.name}}\` is declared but not defined
2481
2848
 
2482
- You can do one of these:
2483
- 1) Remove declaration of \`{${parameter.name}}\`
2484
- 2) Add task that results in \`-> {${parameter.name}}\`
2849
+ You can do one of these:
2850
+ 1) Remove declaration of \`{${parameter.name}}\`
2851
+ 2) Add task that results in \`-> {${parameter.name}}\`
2485
2852
 
2486
- ${block(pipelineIdentification)}
2487
- `));
2488
- }
2853
+ ${block(pipelineIdentification)}
2854
+ `));
2855
+ }
2856
+ /**
2857
+ * Checks whether one parameter is consumed by at least one task dependency list.
2858
+ *
2859
+ * @private internal utility of `validatePipeline`
2860
+ */
2861
+ function isParameterUsedByAnyTask(parameter, tasks) {
2862
+ return tasks.some((task) => task.dependentParameterNames.includes(parameter.name));
2863
+ }
2864
+ /**
2865
+ * Checks whether one parameter is produced by at least one task.
2866
+ *
2867
+ * @private internal utility of `validatePipeline`
2868
+ */
2869
+ function isParameterDefinedByAnyTask(parameter, tasks) {
2870
+ return tasks.some((task) => task.resultingParameterName === parameter.name);
2871
+ }
2872
+ /**
2873
+ * Collects the parameter names that are already defined before task validation starts.
2874
+ *
2875
+ * @private internal utility of `validatePipeline`
2876
+ */
2877
+ function createInitiallyDefinedParameters(pipeline) {
2878
+ return new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2879
+ }
2880
+ /**
2881
+ * Validates one task result parameter declaration and marks it as defined.
2882
+ *
2883
+ * @private internal utility of `validatePipeline`
2884
+ */
2885
+ function validateTaskResultingParameter(task, definedParameters, pipelineIdentification) {
2886
+ if (definedParameters.has(task.resultingParameterName)) {
2887
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2888
+ Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2889
+
2890
+ ${block(pipelineIdentification)}
2891
+ `));
2489
2892
  }
2490
- // Note: All input parameters are defined - so that they can be used as result of some task
2491
- const definedParameters = new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2492
- // Note: Checking each task individually
2493
- for (const task of pipeline.tasks) {
2494
- if (definedParameters.has(task.resultingParameterName)) {
2495
- throw new PipelineLogicError(spaceTrim$1((block) => `
2496
- Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2893
+ if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2894
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2895
+ Parameter name {${task.resultingParameterName}} is reserved, please use different name
2497
2896
 
2498
- ${block(pipelineIdentification)}
2499
- `));
2500
- }
2501
- if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2502
- throw new PipelineLogicError(spaceTrim$1((block) => `
2503
- Parameter name {${task.resultingParameterName}} is reserved, please use different name
2897
+ ${block(pipelineIdentification)}
2898
+ `));
2899
+ }
2900
+ definedParameters.add(task.resultingParameterName);
2901
+ }
2902
+ /**
2903
+ * Validates joker parameters for one task.
2904
+ *
2905
+ * @private internal utility of `validatePipeline`
2906
+ */
2907
+ function validateTaskJokers(task, pipelineIdentification) {
2908
+ if (!hasTaskJokers(task)) {
2909
+ return;
2910
+ }
2911
+ validateTaskSupportsJokers(task, pipelineIdentification);
2912
+ validateTaskJokerDependencies(task, pipelineIdentification);
2913
+ }
2914
+ /**
2915
+ * Checks whether one task declares any joker parameters.
2916
+ *
2917
+ * @private internal utility of `validatePipeline`
2918
+ */
2919
+ function hasTaskJokers(task) {
2920
+ return !!task.jokerParameterNames && task.jokerParameterNames.length > 0;
2921
+ }
2922
+ /**
2923
+ * Validates that a task has the required supporting features when using jokers.
2924
+ *
2925
+ * @private internal utility of `validatePipeline`
2926
+ */
2927
+ function validateTaskSupportsJokers(task, pipelineIdentification) {
2928
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2929
+ return;
2930
+ }
2931
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2932
+ Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2504
2933
 
2505
- ${block(pipelineIdentification)}
2506
- `));
2934
+ ${block(pipelineIdentification)}
2935
+ `));
2936
+ }
2937
+ /**
2938
+ * Validates that every joker parameter is also listed among task dependencies.
2939
+ *
2940
+ * @private internal utility of `validatePipeline`
2941
+ */
2942
+ function validateTaskJokerDependencies(task, pipelineIdentification) {
2943
+ for (const joker of task.jokerParameterNames) {
2944
+ if (task.dependentParameterNames.includes(joker)) {
2945
+ continue;
2507
2946
  }
2508
- definedParameters.add(task.resultingParameterName);
2509
- if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
2510
- if (!task.format &&
2511
- !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2512
- throw new PipelineLogicError(spaceTrim$1((block) => `
2513
- Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2514
-
2515
- ${block(pipelineIdentification)}
2516
- `));
2517
- }
2518
- for (const joker of task.jokerParameterNames) {
2519
- if (!task.dependentParameterNames.includes(joker)) {
2520
- throw new PipelineLogicError(spaceTrim$1((block) => `
2521
- Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2947
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2948
+ Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2522
2949
 
2523
- ${block(pipelineIdentification)}
2524
- `));
2525
- }
2526
- }
2527
- }
2528
- if (task.expectations) {
2529
- for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2530
- if (min !== undefined && max !== undefined && min > max) {
2531
- throw new PipelineLogicError(spaceTrim$1((block) => `
2532
- Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2950
+ ${block(pipelineIdentification)}
2951
+ `));
2952
+ }
2953
+ }
2954
+ /**
2955
+ * Validates all expectation bounds configured on one task.
2956
+ *
2957
+ * @private internal utility of `validatePipeline`
2958
+ */
2959
+ function validateTaskExpectations(task, pipelineIdentification) {
2960
+ if (!task.expectations) {
2961
+ return;
2962
+ }
2963
+ for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2964
+ validateTaskExpectationRange(unit, min, max, pipelineIdentification);
2965
+ validateTaskExpectationMin(unit, min, pipelineIdentification);
2966
+ validateTaskExpectationMax(unit, max, pipelineIdentification);
2967
+ }
2968
+ }
2969
+ /**
2970
+ * Validates the minimum and maximum expectation ordering for one unit.
2971
+ *
2972
+ * @private internal utility of `validatePipeline`
2973
+ */
2974
+ function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
2975
+ if (min === undefined || max === undefined || min <= max) {
2976
+ return;
2977
+ }
2978
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2979
+ Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2533
2980
 
2534
- ${block(pipelineIdentification)}
2535
- `));
2536
- }
2537
- if (min !== undefined && min < 0) {
2538
- throw new PipelineLogicError(spaceTrim$1((block) => `
2539
- Min expectation of ${unit} must be zero or positive
2981
+ ${block(pipelineIdentification)}
2982
+ `));
2983
+ }
2984
+ /**
2985
+ * Validates the minimum expectation bound for one unit.
2986
+ *
2987
+ * @private internal utility of `validatePipeline`
2988
+ */
2989
+ function validateTaskExpectationMin(unit, min, pipelineIdentification) {
2990
+ if (min === undefined || min >= 0) {
2991
+ return;
2992
+ }
2993
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2994
+ Min expectation of ${unit} must be zero or positive
2540
2995
 
2541
- ${block(pipelineIdentification)}
2542
- `));
2543
- }
2544
- if (max !== undefined && max <= 0) {
2545
- throw new PipelineLogicError(spaceTrim$1((block) => `
2546
- Max expectation of ${unit} must be positive
2996
+ ${block(pipelineIdentification)}
2997
+ `));
2998
+ }
2999
+ /**
3000
+ * Validates the maximum expectation bound for one unit.
3001
+ *
3002
+ * @private internal utility of `validatePipeline`
3003
+ */
3004
+ function validateTaskExpectationMax(unit, max, pipelineIdentification) {
3005
+ if (max === undefined || max > 0) {
3006
+ return;
3007
+ }
3008
+ throw new PipelineLogicError(spaceTrim$1((block) => `
3009
+ Max expectation of ${unit} must be positive
2547
3010
 
2548
- ${block(pipelineIdentification)}
2549
- `));
2550
- }
2551
- }
2552
- }
3011
+ ${block(pipelineIdentification)}
3012
+ `));
3013
+ }
3014
+ /**
3015
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
3016
+ *
3017
+ * @private internal utility of `validatePipeline`
3018
+ */
3019
+ function createInitialDependencyResolutionState(pipeline) {
3020
+ return {
3021
+ resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
3022
+ unresolvedTasks: [...pipeline.tasks],
3023
+ };
3024
+ }
3025
+ /**
3026
+ * Checks whether dependency resolution still has tasks left to process.
3027
+ *
3028
+ * @private internal utility of `validatePipeline`
3029
+ */
3030
+ function hasUnresolvedTasks({ unresolvedTasks }) {
3031
+ return unresolvedTasks.length > 0;
3032
+ }
3033
+ /**
3034
+ * Resolves the next batch of currently satisfiable tasks.
3035
+ *
3036
+ * @private internal utility of `validatePipeline`
3037
+ */
3038
+ function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
3039
+ const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
3040
+ if (currentlyResolvedTasks.length === 0) {
3041
+ throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
2553
3042
  }
2554
- // Note: Detect circular dependencies
2555
- let resovedParameters = pipeline.parameters
3043
+ return {
3044
+ resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
3045
+ unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
3046
+ };
3047
+ }
3048
+ /**
3049
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
3050
+ *
3051
+ * @private internal utility of `validatePipeline`
3052
+ */
3053
+ function createInitiallyResolvedParameterNames(pipeline) {
3054
+ let resolvedParameterNames = pipeline.parameters
2556
3055
  .filter(({ isInput }) => isInput)
2557
3056
  .map(({ name }) => name);
2558
- // Note: All reserved parameters are resolved
2559
3057
  for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
2560
- resovedParameters = [...resovedParameters, reservedParameterName];
3058
+ resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
2561
3059
  }
2562
- let unresovedTasks = [...pipeline.tasks];
2563
- let loopLimit = LOOP_LIMIT;
2564
- while (unresovedTasks.length > 0) {
2565
- if (loopLimit-- < 0) {
2566
- // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2567
- throw new UnexpectedError(spaceTrim$1((block) => `
2568
- Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2569
-
2570
- ${block(pipelineIdentification)}
2571
- `));
2572
- }
2573
- const currentlyResovedTasks = unresovedTasks.filter((task) => task.dependentParameterNames.every((name) => resovedParameters.includes(name)));
2574
- if (currentlyResovedTasks.length === 0) {
2575
- throw new PipelineLogicError(
2576
- // TODO: [🐎] DRY
2577
- spaceTrim$1((block) => `
3060
+ return resolvedParameterNames;
3061
+ }
3062
+ /**
3063
+ * Adds newly resolved task outputs to the resolved parameter list.
3064
+ *
3065
+ * @private internal utility of `validatePipeline`
3066
+ */
3067
+ function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
3068
+ return [
3069
+ ...resolvedParameterNames,
3070
+ ...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
3071
+ ];
3072
+ }
3073
+ /**
3074
+ * Selects tasks whose dependencies are already resolved.
3075
+ *
3076
+ * @private internal utility of `validatePipeline`
3077
+ */
3078
+ function getCurrentlyResolvedTasks(unresolvedTasks, resolvedParameterNames) {
3079
+ return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
3080
+ }
3081
+ /**
3082
+ * Creates the unexpected loop-limit error for dependency resolution.
3083
+ *
3084
+ * @private internal utility of `validatePipeline`
3085
+ */
3086
+ function createDependencyResolutionLoopLimitError(pipelineIdentification) {
3087
+ // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
3088
+ return new UnexpectedError(spaceTrim$1((block) => `
3089
+ Loop limit reached during detection of circular dependencies in \`validatePipeline\`
3090
+
3091
+ ${block(pipelineIdentification)}
3092
+ `));
3093
+ }
3094
+ /**
3095
+ * Creates the detailed error for unresolved or circular task dependencies.
3096
+ *
3097
+ * @private internal utility of `validatePipeline`
3098
+ */
3099
+ function createUnresolvedTasksError(unresolvedTasks, resolvedParameterNames, pipelineIdentification) {
3100
+ return new PipelineLogicError(
3101
+ // TODO: [🐎] DRY
3102
+ spaceTrim$1((block) => `
2578
3103
 
2579
- Can not resolve some parameters:
2580
- Either you are using a parameter that is not defined, or there are some circular dependencies.
3104
+ Can not resolve some parameters:
3105
+ Either you are using a parameter that is not defined, or there are some circular dependencies.
2581
3106
 
2582
- ${block(pipelineIdentification)}
3107
+ ${block(pipelineIdentification)}
2583
3108
 
2584
- **Can not resolve:**
2585
- ${block(unresovedTasks
2586
- .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2587
- .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2588
- .join(' and ')}`)
2589
- .join('\n'))}
3109
+ **Can not resolve:**
3110
+ ${block(unresolvedTasks
3111
+ .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
3112
+ .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
3113
+ .join(' and ')}`)
3114
+ .join('\n'))}
2590
3115
 
2591
- **Resolved:**
2592
- ${block(resovedParameters
2593
- .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2594
- .map((name) => `- Parameter \`{${name}}\``)
2595
- .join('\n'))}
3116
+ **Resolved:**
3117
+ ${block(resolvedParameterNames
3118
+ .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
3119
+ .map((name) => `- Parameter \`{${name}}\``)
3120
+ .join('\n'))}
2596
3121
 
2597
3122
 
2598
- **Reserved (which are available):**
2599
- ${block(resovedParameters
2600
- .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2601
- .map((name) => `- Parameter \`{${name}}\``)
2602
- .join('\n'))}
3123
+ **Reserved (which are available):**
3124
+ ${block(resolvedParameterNames
3125
+ .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
3126
+ .map((name) => `- Parameter \`{${name}}\``)
3127
+ .join('\n'))}
2603
3128
 
2604
3129
 
2605
- `));
2606
- }
2607
- resovedParameters = [
2608
- ...resovedParameters,
2609
- ...currentlyResovedTasks.map(({ resultingParameterName }) => resultingParameterName),
2610
- ];
2611
- unresovedTasks = unresovedTasks.filter((task) => !currentlyResovedTasks.includes(task));
2612
- }
2613
- // Note: Check that formfactor is corresponding to the pipeline interface
2614
- // TODO: !!6 Implement this
2615
- // pipeline.formfactorName
3130
+ `));
2616
3131
  }
2617
3132
  /**
2618
3133
  * TODO: [🧞‍♀️] Do not allow joker + foreach
@@ -3290,70 +3805,281 @@ function assertsTaskSuccessful(executionResult) {
3290
3805
  // TODO: [🧠] Can this return type be better typed than void
3291
3806
 
3292
3807
  /**
3293
- * Helper to create a new task
3808
+ * Resolves the short task summary shown in the UI.
3809
+ *
3810
+ * @private internal helper function of `ExecutionTask`
3811
+ */
3812
+ function resolveTaskTldr(options) {
3813
+ const { customTldr } = options;
3814
+ if (customTldr) {
3815
+ return customTldr;
3816
+ }
3817
+ return {
3818
+ percent: resolveTaskPercent(options),
3819
+ message: `${resolveTaskMessage(options)} (!!!fallback)`,
3820
+ };
3821
+ }
3822
+ /**
3823
+ * Resolves the best progress percentage for the current task state.
3824
+ *
3825
+ * @private internal helper function of `ExecutionTask`
3826
+ */
3827
+ function resolveTaskPercent(options) {
3828
+ const explicitPercent = getExplicitTaskPercent(options.currentValue);
3829
+ if (typeof explicitPercent === 'number') {
3830
+ return normalizeTaskPercent(explicitPercent);
3831
+ }
3832
+ return normalizeTaskPercent(calculateSimulatedTaskPercent(options));
3833
+ }
3834
+ /**
3835
+ * Picks a directly reported progress percentage from the task result snapshot.
3836
+ *
3837
+ * @private internal helper function of `ExecutionTask`
3838
+ */
3839
+ function getExplicitTaskPercent(currentValue) {
3840
+ var _a, _b, _c, _d, _e, _f;
3841
+ 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);
3842
+ }
3843
+ /**
3844
+ * Simulates progress when the task result does not expose an explicit percentage.
3845
+ *
3846
+ * @private internal helper function of `ExecutionTask`
3847
+ */
3848
+ function calculateSimulatedTaskPercent(options) {
3849
+ const { currentValue, status, createdAt } = options;
3850
+ const elapsedMs = new Date().getTime() - createdAt.getTime();
3851
+ const timeProgress = Math.min(elapsedMs / DEFAULT_TASK_SIMULATED_DURATION_MS, 1);
3852
+ const { subtaskCount, completedSubtasks } = summarizeTaskSubtasks(currentValue);
3853
+ if (status === 'FINISHED') {
3854
+ return 1;
3855
+ }
3856
+ if (status === 'ERROR') {
3857
+ return 0;
3858
+ }
3859
+ return Math.min(completedSubtasks / subtaskCount + (1 / subtaskCount) * timeProgress, 1);
3860
+ }
3861
+ /**
3862
+ * Counts total and completed subtasks used by the fallback progress simulation.
3863
+ *
3864
+ * @private internal helper function of `ExecutionTask`
3865
+ */
3866
+ function summarizeTaskSubtasks(currentValue) {
3867
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks)) {
3868
+ return { subtaskCount: 1, completedSubtasks: 0 };
3869
+ }
3870
+ return {
3871
+ subtaskCount: currentValue.subtasks.length || 1,
3872
+ completedSubtasks: currentValue.subtasks.filter(isTaskSubtaskCompleted).length,
3873
+ };
3874
+ }
3875
+ /**
3876
+ * Tells whether a task subtask is already finished.
3877
+ *
3878
+ * @private internal helper function of `ExecutionTask`
3879
+ */
3880
+ function isTaskSubtaskCompleted(subtask) {
3881
+ return subtask.done || subtask.completed || false;
3882
+ }
3883
+ /**
3884
+ * Normalizes a progress percentage into the expected `0..1` range.
3885
+ *
3886
+ * @private internal helper function of `ExecutionTask`
3887
+ */
3888
+ function normalizeTaskPercent(percentRaw) {
3889
+ let percent = Number(percentRaw) || 0;
3890
+ if (percent < 0) {
3891
+ percent = 0;
3892
+ }
3893
+ if (percent > 1) {
3894
+ percent = 1;
3895
+ }
3896
+ return percent;
3897
+ }
3898
+ /**
3899
+ * Resolves the best human-readable status message for the current task state.
3900
+ *
3901
+ * @private internal helper function of `ExecutionTask`
3902
+ */
3903
+ function resolveTaskMessage(options) {
3904
+ return (getCurrentValueMessage(options.currentValue) ||
3905
+ getCurrentSubtaskMessage(options.currentValue) ||
3906
+ getLatestIssueMessage(options.errors, 'Error') ||
3907
+ getLatestIssueMessage(options.warnings, 'Warning') ||
3908
+ getStatusMessage(options.status));
3909
+ }
3910
+ /**
3911
+ * Picks a message already reported by the current task result snapshot.
3912
+ *
3913
+ * @private internal helper function of `ExecutionTask`
3914
+ */
3915
+ function getCurrentValueMessage(currentValue) {
3916
+ var _a, _b, _c, _d;
3917
+ 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;
3918
+ }
3919
+ /**
3920
+ * Builds a fallback message from the first unfinished subtask title.
3921
+ *
3922
+ * @private internal helper function of `ExecutionTask`
3923
+ */
3924
+ function getCurrentSubtaskMessage(currentValue) {
3925
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks) || currentValue.subtasks.length === 0) {
3926
+ return undefined;
3927
+ }
3928
+ const currentSubtask = currentValue.subtasks.find((subtask) => !isTaskSubtaskCompleted(subtask));
3929
+ if (!(currentSubtask === null || currentSubtask === void 0 ? void 0 : currentSubtask.title)) {
3930
+ return undefined;
3931
+ }
3932
+ return `Working on ${currentSubtask.title}`;
3933
+ }
3934
+ /**
3935
+ * Picks the latest error or warning message, with the legacy generic fallback label.
3936
+ *
3937
+ * @private internal helper function of `ExecutionTask`
3938
+ */
3939
+ function getLatestIssueMessage(issues, fallbackMessage) {
3940
+ if (issues.length === 0) {
3941
+ return undefined;
3942
+ }
3943
+ return issues[issues.length - 1].message || fallbackMessage;
3944
+ }
3945
+ /**
3946
+ * Builds the final status-based fallback message.
3947
+ *
3948
+ * @private internal helper function of `ExecutionTask`
3949
+ */
3950
+ function getStatusMessage(status) {
3951
+ if (status === 'FINISHED') {
3952
+ return 'Finished';
3953
+ }
3954
+ if (status === 'ERROR') {
3955
+ return 'Error';
3956
+ }
3957
+ return 'Running';
3958
+ }
3959
+
3960
+ /**
3961
+ * Creates the initial mutable state for a task.
3294
3962
  *
3295
3963
  * @private internal helper function
3296
3964
  */
3297
- function createTask(options) {
3298
- const { taskType, taskProcessCallback } = options;
3299
- let { title } = options;
3300
- // TODO: [🐙] DRY
3301
- const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3302
- let status = 'RUNNING';
3303
- const createdAt = new Date();
3304
- let updatedAt = createdAt;
3305
- const errors = [];
3306
- const warnings = [];
3307
- const llmCalls = [];
3308
- let currentValue = {};
3309
- let customTldr = null;
3310
- const partialResultSubject = new Subject();
3311
- // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3312
- const finalResultPromise = /* not await */ taskProcessCallback((newOngoingResult) => {
3965
+ function createTaskState(title, createdAt) {
3966
+ return {
3967
+ title,
3968
+ status: 'RUNNING',
3969
+ updatedAt: createdAt,
3970
+ errors: [],
3971
+ warnings: [],
3972
+ llmCalls: [],
3973
+ currentValue: {},
3974
+ customTldr: null,
3975
+ };
3976
+ }
3977
+ /**
3978
+ * Creates the partial-result updater passed into the task process callback.
3979
+ *
3980
+ * @private internal helper function
3981
+ */
3982
+ function createOngoingResultUpdater(taskState, partialResultSubject) {
3983
+ return (newOngoingResult) => {
3313
3984
  if (newOngoingResult.title) {
3314
- title = newOngoingResult.title;
3985
+ taskState.title = newOngoingResult.title;
3315
3986
  }
3316
- updatedAt = new Date();
3317
- Object.assign(currentValue, newOngoingResult);
3987
+ taskState.updatedAt = new Date();
3988
+ Object.assign(taskState.currentValue, newOngoingResult);
3318
3989
  // <- TODO: assign deep
3319
3990
  partialResultSubject.next(newOngoingResult);
3320
- }, (tldrInfo) => {
3321
- customTldr = tldrInfo;
3322
- updatedAt = new Date();
3323
- }, (llmCall) => {
3324
- llmCalls.push(llmCall);
3325
- updatedAt = new Date();
3326
- });
3991
+ };
3992
+ }
3993
+ /**
3994
+ * Creates the custom-TLDR updater passed into the task process callback.
3995
+ *
3996
+ * @private internal helper function
3997
+ */
3998
+ function createTldrUpdater(taskState) {
3999
+ return (tldrInfo) => {
4000
+ taskState.customTldr = tldrInfo;
4001
+ taskState.updatedAt = new Date();
4002
+ };
4003
+ }
4004
+ /**
4005
+ * Creates the LLM call logger passed into the task process callback.
4006
+ *
4007
+ * @private internal helper function
4008
+ */
4009
+ function createLlmCallLogger(taskState) {
4010
+ return (llmCall) => {
4011
+ taskState.llmCalls.push(llmCall);
4012
+ taskState.updatedAt = new Date();
4013
+ };
4014
+ }
4015
+ /**
4016
+ * Wires the task promise into the observable/error lifecycle.
4017
+ *
4018
+ * @private internal helper function
4019
+ */
4020
+ function settleTaskPromise(finalResultPromise, taskState, partialResultSubject) {
3327
4021
  finalResultPromise
3328
4022
  .catch((error) => {
3329
- errors.push(error);
4023
+ taskState.errors.push(error);
3330
4024
  partialResultSubject.error(error);
3331
4025
  })
3332
4026
  .then((executionResult) => {
3333
4027
  if (executionResult) {
3334
4028
  try {
3335
- updatedAt = new Date();
3336
- errors.push(...executionResult.errors);
3337
- warnings.push(...executionResult.warnings);
3338
- // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3339
- // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3340
- // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3341
- // And delete `ExecutionTask.currentValue.preparedPipeline`
3342
- assertsTaskSuccessful(executionResult);
3343
- status = 'FINISHED';
3344
- currentValue = jsonStringsToJsons(executionResult);
3345
- // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3346
- partialResultSubject.next(executionResult);
4029
+ finalizeTaskResult(executionResult, taskState, partialResultSubject);
3347
4030
  }
3348
4031
  catch (error) {
3349
- assertsError(error);
3350
- status = 'ERROR';
3351
- errors.push(error);
3352
- partialResultSubject.error(error);
4032
+ failTaskResult(error, taskState, partialResultSubject);
3353
4033
  }
3354
4034
  }
3355
4035
  partialResultSubject.complete();
3356
4036
  });
4037
+ }
4038
+ /**
4039
+ * Applies the final successful task result into the mutable task state.
4040
+ *
4041
+ * @private internal helper function
4042
+ */
4043
+ function finalizeTaskResult(executionResult, taskState, partialResultSubject) {
4044
+ taskState.updatedAt = new Date();
4045
+ taskState.errors.push(...executionResult.errors);
4046
+ taskState.warnings.push(...executionResult.warnings);
4047
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
4048
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
4049
+ // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
4050
+ // And delete `ExecutionTask.currentValue.preparedPipeline`
4051
+ assertsTaskSuccessful(executionResult);
4052
+ taskState.status = 'FINISHED';
4053
+ taskState.currentValue = jsonStringsToJsons(executionResult);
4054
+ // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
4055
+ partialResultSubject.next(executionResult);
4056
+ }
4057
+ /**
4058
+ * Records a final-result failure after the task promise itself resolved.
4059
+ *
4060
+ * @private internal helper function
4061
+ */
4062
+ function failTaskResult(error, taskState, partialResultSubject) {
4063
+ assertsError(error);
4064
+ taskState.status = 'ERROR';
4065
+ taskState.errors.push(error);
4066
+ partialResultSubject.error(error);
4067
+ }
4068
+ /**
4069
+ * Helper to create a new task
4070
+ *
4071
+ * @private internal helper function
4072
+ */
4073
+ function createTask(options) {
4074
+ const { taskType, title, taskProcessCallback } = options;
4075
+ // TODO: [🐙] DRY
4076
+ const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
4077
+ const createdAt = new Date();
4078
+ const taskState = createTaskState(title, createdAt);
4079
+ const partialResultSubject = new Subject();
4080
+ // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
4081
+ const finalResultPromise = /* not await */ taskProcessCallback(createOngoingResultUpdater(taskState, partialResultSubject), createTldrUpdater(taskState), createLlmCallLogger(taskState));
4082
+ settleTaskPromise(finalResultPromise, taskState, partialResultSubject);
3357
4083
  async function asPromise(options) {
3358
4084
  const { isCrashedOnError = true } = options || {};
3359
4085
  const finalResult = await finalResultPromise;
@@ -3369,91 +4095,29 @@ function createTask(options) {
3369
4095
  return PROMPTBOOK_ENGINE_VERSION;
3370
4096
  },
3371
4097
  get title() {
3372
- return title;
4098
+ return taskState.title;
3373
4099
  // <- Note: [1] These must be getters to allow changing the value in the future
3374
4100
  },
3375
4101
  get status() {
3376
- return status;
4102
+ return taskState.status;
3377
4103
  // <- Note: [1] --||--
3378
4104
  },
3379
4105
  get tldr() {
3380
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3381
- // Use custom tldr if available
3382
- if (customTldr) {
3383
- return customTldr;
3384
- }
3385
- // Fallback to default implementation
3386
- const cv = currentValue;
3387
- // If explicit percent is provided, use it
3388
- 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;
3389
- // Simulate progress if not provided
3390
- if (typeof percentRaw !== 'number') {
3391
- // Simulate progress: evenly split across subtasks, based on elapsed time
3392
- const now = new Date();
3393
- const elapsedMs = now.getTime() - createdAt.getTime();
3394
- const totalMs = DEFAULT_TASK_SIMULATED_DURATION_MS;
3395
- // If subtasks are defined, split progress evenly
3396
- const subtaskCount = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) ? cv.subtasks.length : 1;
3397
- const completedSubtasks = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks)
3398
- ? cv.subtasks.filter((s) => s.done || s.completed).length
3399
- : 0;
3400
- // Progress from completed subtasks
3401
- const subtaskProgress = subtaskCount > 0 ? completedSubtasks / subtaskCount : 0;
3402
- // Progress from elapsed time for current subtask
3403
- const timeProgress = Math.min(elapsedMs / totalMs, 1);
3404
- // Combine: completed subtasks + time progress for current subtask
3405
- percentRaw = Math.min(subtaskProgress + (1 / subtaskCount) * timeProgress, 1);
3406
- if (status === 'FINISHED')
3407
- percentRaw = 1;
3408
- if (status === 'ERROR')
3409
- percentRaw = 0;
3410
- }
3411
- // Clamp to [0,1]
3412
- let percent = Number(percentRaw) || 0;
3413
- if (percent < 0)
3414
- percent = 0;
3415
- if (percent > 1)
3416
- percent = 1;
3417
- // Build a short message: prefer explicit tldr.message, then common summary/message fields, then errors/warnings, then status
3418
- 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;
3419
- let message = messageFromResult;
3420
- if (!message) {
3421
- // If subtasks, show current subtask
3422
- if (Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) && cv.subtasks.length > 0) {
3423
- const current = cv.subtasks.find((s) => !s.done && !s.completed);
3424
- if (current && current.title) {
3425
- message = `Working on ${current.title}`;
3426
- }
3427
- }
3428
- if (!message) {
3429
- if (errors.length) {
3430
- message = errors[errors.length - 1].message || 'Error';
3431
- }
3432
- else if (warnings.length) {
3433
- message = warnings[warnings.length - 1].message || 'Warning';
3434
- }
3435
- else if (status === 'FINISHED') {
3436
- message = 'Finished';
3437
- }
3438
- else if (status === 'ERROR') {
3439
- message = 'Error';
3440
- }
3441
- else {
3442
- message = 'Running';
3443
- }
3444
- }
3445
- }
3446
- return {
3447
- percent: percent,
3448
- message: message + ' (!!!fallback)',
3449
- };
4106
+ return resolveTaskTldr({
4107
+ customTldr: taskState.customTldr,
4108
+ currentValue: taskState.currentValue,
4109
+ status: taskState.status,
4110
+ createdAt,
4111
+ errors: taskState.errors,
4112
+ warnings: taskState.warnings,
4113
+ });
3450
4114
  },
3451
4115
  get createdAt() {
3452
4116
  return createdAt;
3453
4117
  // <- Note: [1] --||--
3454
4118
  },
3455
4119
  get updatedAt() {
3456
- return updatedAt;
4120
+ return taskState.updatedAt;
3457
4121
  // <- Note: [1] --||--
3458
4122
  },
3459
4123
  asPromise,
@@ -3461,19 +4125,19 @@ function createTask(options) {
3461
4125
  return partialResultSubject.asObservable();
3462
4126
  },
3463
4127
  get errors() {
3464
- return errors;
4128
+ return taskState.errors;
3465
4129
  // <- Note: [1] --||--
3466
4130
  },
3467
4131
  get warnings() {
3468
- return warnings;
4132
+ return taskState.warnings;
3469
4133
  // <- Note: [1] --||--
3470
4134
  },
3471
4135
  get llmCalls() {
3472
- return [...llmCalls, { foo: '!!! bar' }];
4136
+ return [...taskState.llmCalls, { foo: '!!! bar' }];
3473
4137
  // <- Note: [1] --||--
3474
4138
  },
3475
4139
  get currentValue() {
3476
- return currentValue;
4140
+ return taskState.currentValue;
3477
4141
  // <- Note: [1] --||--
3478
4142
  },
3479
4143
  };
@@ -4754,210 +5418,275 @@ const promptbookFetch = async (urlOrRequest, init) => {
4754
5418
  * @public exported from `@promptbook/core`
4755
5419
  */
4756
5420
  async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
5421
+ const { knowledgeSourceContent } = knowledgeSource;
5422
+ const name = knowledgeSource.name || knowledgeSourceContentToName(knowledgeSourceContent);
5423
+ const { rootDirname = null, isVerbose = DEFAULT_IS_VERBOSE } = options || {};
5424
+ if (isValidUrl(knowledgeSourceContent)) {
5425
+ return makeUrlKnowledgeSourceHandler(knowledgeSourceContent, name, tools, options, isVerbose);
5426
+ }
5427
+ if (isValidFilePath(knowledgeSourceContent)) {
5428
+ return makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose);
5429
+ }
5430
+ return makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose);
5431
+ }
5432
+ /**
5433
+ * Creates a source handler for URL-based knowledge.
5434
+ *
5435
+ * @private internal utility of `makeKnowledgeSourceHandler`
5436
+ */
5437
+ async function makeUrlKnowledgeSourceHandler(url, name, tools, options, isVerbose) {
4757
5438
  var _a;
4758
5439
  const { fetch = promptbookFetch } = tools;
4759
- const { knowledgeSourceContent } = knowledgeSource;
4760
- let { name } = knowledgeSource;
4761
- const { rootDirname = null,
4762
- // <- TODO: process.cwd() if running in Node.js
4763
- isVerbose = DEFAULT_IS_VERBOSE, } = options || {};
4764
- if (!name) {
4765
- name = knowledgeSourceContentToName(knowledgeSourceContent);
5440
+ if (isVerbose) {
5441
+ console.info(`📄 [1] "${name}" is available at "${url}"`);
4766
5442
  }
4767
- if (isValidUrl(knowledgeSourceContent)) {
4768
- const url = knowledgeSourceContent;
4769
- if (isVerbose) {
4770
- console.info(`📄 [1] "${name}" is available at "${url}"`);
4771
- }
4772
- const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
4773
- const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
4774
- if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
4775
- if (isVerbose) {
4776
- console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
4777
- }
4778
- return {
4779
- source: name,
4780
- filename: null,
4781
- url,
4782
- mimeType,
4783
- /*
4784
- TODO: [🥽]
4785
- > async asBlob() {
4786
- > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
4787
- > const content = await response.blob();
4788
- > return content;
4789
- > },
4790
- */
4791
- async asJson() {
4792
- // TODO: [👨🏻‍🤝‍👨🏻]
4793
- const content = await response.json();
4794
- return content;
4795
- },
4796
- async asText() {
4797
- // TODO: [👨🏻‍🤝‍👨🏻]
4798
- const content = await response.text();
4799
- return content;
4800
- },
4801
- };
4802
- }
4803
- const basename = url.split('/').pop() || titleToName(url);
4804
- const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
4805
- // <- TODO: [🥬] Encapsulate sha256 to some private utility function
4806
- const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
4807
- const filepath = join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
4808
- // Note: Try to create cache directory, but don't fail if filesystem has issues
4809
- try {
4810
- await tools.fs.mkdir(dirname(join(rootDirname, filepath)), { recursive: true });
4811
- }
4812
- catch (error) {
4813
- if (isVerbose) {
4814
- console.info(`📄 [3] "${name}" error creating cache directory`);
4815
- }
4816
- // Note: If we can't create cache directory, we'll handle it when trying to write the file
4817
- // This handles read-only filesystems, permission issues, and missing parent directories
4818
- if (error instanceof Error &&
4819
- (error.message.includes('EROFS') ||
4820
- error.message.includes('read-only') ||
4821
- error.message.includes('EACCES') ||
4822
- error.message.includes('EPERM') ||
4823
- error.message.includes('ENOENT'))) ;
4824
- else {
4825
- // Re-throw other unexpected errors
4826
- throw error;
4827
- }
4828
- }
4829
- const fileContent = Buffer.from(await response.arrayBuffer());
4830
- if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
4831
- 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.`);
4832
- }
4833
- // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
4834
- try {
4835
- await tools.fs.writeFile(join(rootDirname, filepath), fileContent);
4836
- }
4837
- catch (error) {
4838
- if (isVerbose) {
4839
- console.info(`📄 [4] "${name}" error writing cache file`);
4840
- }
4841
- // Note: If we can't write to cache, we'll process the file directly from memory
4842
- // This handles read-only filesystems like Vercel
4843
- if (error instanceof Error &&
4844
- (error.message.includes('EROFS') ||
4845
- error.message.includes('read-only') ||
4846
- error.message.includes('EACCES') ||
4847
- error.message.includes('EPERM') ||
4848
- error.message.includes('ENOENT'))) {
4849
- // Return a handler that works directly with the downloaded content
4850
- return {
4851
- source: name,
4852
- filename: null,
4853
- url,
4854
- mimeType,
4855
- async asJson() {
4856
- return JSON.parse(fileContent.toString('utf-8'));
4857
- },
4858
- async asText() {
4859
- return fileContent.toString('utf-8');
4860
- },
4861
- };
4862
- }
4863
- else {
4864
- // Re-throw other unexpected errors
4865
- throw error;
4866
- }
4867
- }
4868
- // TODO: [💵] Check the file security
4869
- // TODO: [🧹][🧠] Delete the file after the scraping is done
4870
- if (isVerbose) {
4871
- console.info(`📄 [5] "${name}" cached at "${join(rootDirname, filepath)}"`);
4872
- }
4873
- return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
4874
- ...options,
4875
- rootDirname,
4876
- });
5443
+ const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
5444
+ const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
5445
+ if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
5446
+ return makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose);
4877
5447
  }
4878
- else if (isValidFilePath(knowledgeSourceContent)) {
4879
- if (tools.fs === undefined) {
4880
- throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
4881
- // <- TODO: [🧠] What is the best error type here`
5448
+ return cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose);
5449
+ }
5450
+ /**
5451
+ * Creates a source handler that reads directly from a fetched response.
5452
+ *
5453
+ * @private internal utility of `makeKnowledgeSourceHandler`
5454
+ */
5455
+ function makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose) {
5456
+ if (isVerbose) {
5457
+ console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
5458
+ }
5459
+ return {
5460
+ source: name,
5461
+ filename: null,
5462
+ url,
5463
+ mimeType,
5464
+ /*
5465
+ TODO: [🥽]
5466
+ > async asBlob() {
5467
+ > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
5468
+ > const content = await response.blob();
5469
+ > return content;
5470
+ > },
5471
+ */
5472
+ async asJson() {
5473
+ // TODO: [👨🏻‍🤝‍👨🏻]
5474
+ const content = await response.json();
5475
+ return content;
5476
+ },
5477
+ async asText() {
5478
+ // TODO: [👨🏻‍🤝‍👨🏻]
5479
+ const content = await response.text();
5480
+ return content;
5481
+ },
5482
+ };
5483
+ }
5484
+ /**
5485
+ * Downloads a PDF knowledge source into cache when possible and falls back to in-memory content otherwise.
5486
+ *
5487
+ * @private internal utility of `makeKnowledgeSourceHandler`
5488
+ */
5489
+ async function cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose) {
5490
+ const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5491
+ const filepath = createDownloadedKnowledgeSourceFilepath(url, mimeType);
5492
+ const fullFilepath = join(rootDirname, filepath);
5493
+ await createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose);
5494
+ const fileContent = Buffer.from(await response.arrayBuffer());
5495
+ if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5496
+ 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.`);
5497
+ }
5498
+ const isCached = await writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose);
5499
+ if (!isCached) {
5500
+ return makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent);
5501
+ }
5502
+ // TODO: [💵] Check the file security
5503
+ // TODO: [🧹][🧠] Delete the file after the scraping is done
5504
+ if (isVerbose) {
5505
+ console.info(`📄 [5] "${name}" cached at "${fullFilepath}"`);
5506
+ }
5507
+ return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
5508
+ ...options,
5509
+ rootDirname,
5510
+ });
5511
+ }
5512
+ /**
5513
+ * Builds a stable cache filepath for a downloaded knowledge source.
5514
+ *
5515
+ * @private internal utility of `makeKnowledgeSourceHandler`
5516
+ */
5517
+ function createDownloadedKnowledgeSourceFilepath(url, mimeType) {
5518
+ const basename = url.split('/').pop() || titleToName(url);
5519
+ const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
5520
+ // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5521
+ return join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
5522
+ }
5523
+ /**
5524
+ * Tries to create the cache directory needed for a downloaded knowledge source.
5525
+ *
5526
+ * @private internal utility of `makeKnowledgeSourceHandler`
5527
+ */
5528
+ async function createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose) {
5529
+ try {
5530
+ await tools.fs.mkdir(dirname(fullFilepath), { recursive: true });
5531
+ }
5532
+ catch (error) {
5533
+ if (isVerbose) {
5534
+ console.info(`📄 [3] "${name}" error creating cache directory`);
4882
5535
  }
4883
- if (rootDirname === null) {
4884
- throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
4885
- // <- TODO: [🧠] What is the best error type here`
5536
+ // Note: If we can't create cache directory, we'll handle it when trying to write the file
5537
+ // This handles read-only filesystems, permission issues, and missing parent directories
5538
+ if (!isIgnorableCacheFilesystemError(error)) {
5539
+ throw error;
4886
5540
  }
4887
- const filename = isAbsolute(knowledgeSourceContent)
4888
- ? knowledgeSourceContent
4889
- : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5541
+ }
5542
+ }
5543
+ /**
5544
+ * Tries to write downloaded content into cache and reports whether the cache was usable.
5545
+ *
5546
+ * @private internal utility of `makeKnowledgeSourceHandler`
5547
+ */
5548
+ async function writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose) {
5549
+ // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
5550
+ try {
5551
+ await tools.fs.writeFile(fullFilepath, fileContent);
5552
+ return true;
5553
+ }
5554
+ catch (error) {
4890
5555
  if (isVerbose) {
4891
- console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5556
+ console.info(`📄 [4] "${name}" error writing cache file`);
4892
5557
  }
4893
- const fileExtension = getFileExtension(filename);
4894
- const mimeType = extensionToMimeType(fileExtension || '');
4895
- if (!(await isFileExisting(filename, tools.fs))) {
4896
- throw new NotFoundError(spaceTrim$1((block) => `
4897
- Can not make source handler for file which does not exist:
5558
+ // Note: If we can't write to cache, we'll process the file directly from memory
5559
+ // This handles read-only filesystems like Vercel
5560
+ if (isIgnorableCacheFilesystemError(error)) {
5561
+ return false;
5562
+ }
5563
+ throw error;
5564
+ }
5565
+ }
5566
+ /**
5567
+ * Detects filesystem errors that should not fail optional caching.
5568
+ *
5569
+ * @private internal utility of `makeKnowledgeSourceHandler`
5570
+ */
5571
+ function isIgnorableCacheFilesystemError(error) {
5572
+ return (error instanceof Error &&
5573
+ (error.message.includes('EROFS') ||
5574
+ error.message.includes('read-only') ||
5575
+ error.message.includes('EACCES') ||
5576
+ error.message.includes('EPERM') ||
5577
+ error.message.includes('ENOENT')));
5578
+ }
5579
+ /**
5580
+ * Creates a source handler backed by already downloaded file content kept in memory.
5581
+ *
5582
+ * @private internal utility of `makeKnowledgeSourceHandler`
5583
+ */
5584
+ function makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent) {
5585
+ return {
5586
+ source: name,
5587
+ filename: null,
5588
+ url,
5589
+ mimeType,
5590
+ async asJson() {
5591
+ return JSON.parse(fileContent.toString('utf-8'));
5592
+ },
5593
+ async asText() {
5594
+ return fileContent.toString('utf-8');
5595
+ },
5596
+ };
5597
+ }
5598
+ /**
5599
+ * Creates a source handler for file-based knowledge.
5600
+ *
5601
+ * @private internal utility of `makeKnowledgeSourceHandler`
5602
+ */
5603
+ async function makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose) {
5604
+ if (tools.fs === undefined) {
5605
+ throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
5606
+ // <- TODO: [🧠] What is the best error type here`
5607
+ }
5608
+ if (rootDirname === null) {
5609
+ throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
5610
+ // <- TODO: [🧠] What is the best error type here`
5611
+ }
5612
+ const filename = isAbsolute(knowledgeSourceContent)
5613
+ ? knowledgeSourceContent
5614
+ : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5615
+ if (isVerbose) {
5616
+ console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5617
+ }
5618
+ const fileExtension = getFileExtension(filename);
5619
+ const mimeType = extensionToMimeType(fileExtension || '');
5620
+ if (!(await isFileExisting(filename, tools.fs))) {
5621
+ throw new NotFoundError(spaceTrim$1((block) => `
5622
+ Can not make source handler for file which does not exist:
4898
5623
 
4899
- File:
4900
- ${block(knowledgeSourceContent)}
5624
+ File:
5625
+ ${block(knowledgeSourceContent)}
4901
5626
 
4902
- Full file path:
4903
- ${block(filename)}
4904
- `));
4905
- }
4906
- // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
4907
- return {
4908
- source: name,
4909
- filename,
4910
- url: null,
4911
- mimeType,
4912
- /*
4913
- TODO: [🥽]
4914
- > async asBlob() {
4915
- > const content = await tools.fs!.readFile(filename);
4916
- > return new Blob(
4917
- > [
4918
- > content,
4919
- > // <- TODO: [🥽] This is NOT tested, test it
4920
- > ],
4921
- > { type: mimeType },
4922
- > );
4923
- > },
4924
- */
4925
- async asJson() {
4926
- return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
4927
- },
4928
- async asText() {
4929
- return await tools.fs.readFile(filename, 'utf-8');
4930
- },
4931
- };
5627
+ Full file path:
5628
+ ${block(filename)}
5629
+ `));
4932
5630
  }
4933
- else {
4934
- if (isVerbose) {
4935
- console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
4936
- console.info('---');
4937
- console.info(knowledgeSourceContent);
4938
- console.info('---');
4939
- }
4940
- return {
4941
- source: name,
4942
- filename: null,
4943
- url: null,
4944
- mimeType: 'text/markdown',
4945
- asText() {
4946
- return knowledgeSource.knowledgeSourceContent;
4947
- },
4948
- asJson() {
4949
- throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
4950
- },
4951
- /*
4952
- TODO: [🥽]
4953
- > asBlob() {
4954
- > throw new UnexpectedError(
4955
- > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
4956
- > );
4957
- > },
4958
- */
4959
- };
5631
+ // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
5632
+ return {
5633
+ source: name,
5634
+ filename,
5635
+ url: null,
5636
+ mimeType,
5637
+ /*
5638
+ TODO: [🥽]
5639
+ > async asBlob() {
5640
+ > const content = await tools.fs!.readFile(filename);
5641
+ > return new Blob(
5642
+ > [
5643
+ > content,
5644
+ > // <- TODO: [🥽] This is NOT tested, test it
5645
+ > ],
5646
+ > { type: mimeType },
5647
+ > );
5648
+ > },
5649
+ */
5650
+ async asJson() {
5651
+ return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
5652
+ },
5653
+ async asText() {
5654
+ return await tools.fs.readFile(filename, 'utf-8');
5655
+ },
5656
+ };
5657
+ }
5658
+ /**
5659
+ * Creates a source handler for inline text knowledge.
5660
+ *
5661
+ * @private internal utility of `makeKnowledgeSourceHandler`
5662
+ */
5663
+ function makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose) {
5664
+ if (isVerbose) {
5665
+ console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
5666
+ console.info('---');
5667
+ console.info(knowledgeSourceContent);
5668
+ console.info('---');
4960
5669
  }
5670
+ return {
5671
+ source: name,
5672
+ filename: null,
5673
+ url: null,
5674
+ mimeType: 'text/markdown',
5675
+ asText() {
5676
+ return knowledgeSourceContent;
5677
+ },
5678
+ asJson() {
5679
+ throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
5680
+ },
5681
+ /*
5682
+ TODO: [🥽]
5683
+ > asBlob() {
5684
+ > throw new UnexpectedError(
5685
+ > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
5686
+ > );
5687
+ > },
5688
+ */
5689
+ };
4961
5690
  }
4962
5691
 
4963
5692
  /**
@@ -6150,204 +6879,102 @@ const CountUtils = {
6150
6879
  // TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
6151
6880
  // Note: [💞] Ignore a discrepancy between file name and entity name
6152
6881
 
6153
- /**
6154
- * Function checkExpectations will check if the expectations on given value are met
6155
- *
6156
- * Note: There are two similar functions:
6157
- * - `checkExpectations` which throws an error if the expectations are not met
6158
- * - `isPassingExpectations` which returns a boolean
6159
- *
6160
- * @throws {ExpectError} if the expectations are not met
6161
- * @returns {void} Nothing
6162
- *
6163
- * @private internal function of `createPipelineExecutor`
6164
- */
6165
- function checkExpectations(expectations, value) {
6166
- for (const [unit, { max, min }] of Object.entries(expectations)) {
6167
- const amount = CountUtils[unit.toUpperCase()](value);
6168
- if (min && amount < min) {
6169
- throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6170
- } /* not else */
6171
- if (max && amount > max) {
6172
- throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6173
- }
6174
- }
6175
- }
6176
- // TODO: [💝] Unite object for expecting amount and format
6177
- // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6178
- // Note: [💝] and [🤠] are interconnected together
6179
-
6180
- /**
6181
- * Validates a prompt result against expectations and format requirements.
6182
- * This function provides a common abstraction for result validation that can be used
6183
- * by both execution logic and caching logic to ensure consistency.
6184
- *
6185
- * Note: [🔂] This function is idempotent.
6186
- *
6187
- * @param options - The validation options including result string, expectations, and format
6188
- * @returns Validation result with processed string and validity status
6189
- *
6190
- * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6191
- */
6192
- function validatePromptResult(options) {
6193
- const { resultString, expectations, format } = options;
6194
- let processedResultString = resultString;
6195
- let validationError;
6196
- try {
6197
- // TODO: [💝] Unite object for expecting amount and format
6198
- if (format) {
6199
- if (format === 'JSON') {
6200
- if (!isValidJsonString(processedResultString)) {
6201
- // TODO: [🏢] Do more universally via `FormatParser`
6202
- try {
6203
- processedResultString = extractJsonBlock(processedResultString);
6204
- }
6205
- catch (error) {
6206
- keepUnused(error);
6207
- throw new ExpectError(spaceTrim$1((block) => `
6208
- Expected valid JSON string
6209
-
6210
- The expected JSON text:
6211
- ${block(processedResultString)}
6212
- `));
6213
- }
6214
- }
6215
- }
6216
- else {
6217
- throw new UnexpectedError(`Unknown format "${format}"`);
6218
- }
6219
- }
6220
- // TODO: [💝] Unite object for expecting amount and format
6221
- if (expectations) {
6222
- checkExpectations(expectations, processedResultString);
6223
- }
6224
- return {
6225
- isValid: true,
6226
- processedResultString,
6227
- };
6228
- }
6229
- catch (error) {
6230
- if (error instanceof ExpectError) {
6231
- validationError = error;
6232
- }
6233
- else {
6234
- // Re-throw non-ExpectError errors (like UnexpectedError)
6235
- throw error;
6236
- }
6237
- return {
6238
- isValid: false,
6239
- processedResultString,
6240
- error: validationError,
6241
- };
6242
- }
6243
- }
6244
-
6245
- /**
6246
- * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
6247
- * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
6248
- * Throws errors if execution fails after all attempts.
6249
- *
6250
- * @param options - The options for execution, including task, parameters, pipeline, and configuration.
6251
- * @returns The result string of the executed task.
6252
- *
6253
- * @private internal utility of `createPipelineExecutor`
6254
- */
6255
- async function executeAttempts(options) {
6256
- const $ongoingTaskResult = createOngoingTaskResult();
6257
- const llmTools = getSingleLlmExecutionTools(options.tools.llm);
6258
- attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
6259
- const attempt = createAttemptDescriptor({
6260
- attemptIndex,
6261
- jokerParameterNames: options.jokerParameterNames,
6262
- pipelineIdentification: options.pipelineIdentification,
6263
- });
6264
- resetAttemptExecutionState($ongoingTaskResult);
6265
- try {
6266
- await executeSingleAttempt({
6267
- attempt,
6268
- options,
6269
- llmTools,
6270
- $ongoingTaskResult,
6271
- });
6272
- break attempts;
6273
- }
6274
- catch (error) {
6275
- if (!(error instanceof ExpectError)) {
6276
- throw error;
6277
- }
6278
- recordFailedAttempt({
6279
- error,
6280
- attemptIndex,
6281
- onProgress: options.onProgress,
6282
- $ongoingTaskResult,
6283
- });
6284
- }
6285
- finally {
6286
- reportPromptExecution({
6287
- attempt,
6288
- task: options.task,
6289
- $executionReport: options.$executionReport,
6290
- logLlmCall: options.logLlmCall,
6291
- $ongoingTaskResult,
6292
- });
6293
- }
6294
- throwIfFinalAttemptFailed({
6295
- attemptIndex,
6296
- maxAttempts: options.maxAttempts,
6297
- maxExecutionAttempts: options.maxExecutionAttempts,
6298
- pipelineIdentification: options.pipelineIdentification,
6299
- $ongoingTaskResult,
6300
- });
6301
- }
6302
- return getSuccessfulResultString({
6303
- pipelineIdentification: options.pipelineIdentification,
6304
- $ongoingTaskResult,
6305
- });
6306
- }
6307
- /**
6308
- * Creates mutable attempt state for one task execution lifecycle.
6309
- */
6310
- function createOngoingTaskResult() {
6311
- return {
6312
- $result: null,
6313
- $resultString: null,
6314
- $expectError: null,
6315
- $scriptPipelineExecutionErrors: [],
6316
- $failedResults: [],
6317
- };
6318
- }
6319
- /**
6320
- * Resolves the bookkeeping for one loop iteration, including joker lookup.
6321
- */
6322
- function createAttemptDescriptor(options) {
6323
- const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
6324
- const isJokerAttempt = attemptIndex < 0;
6325
- const jokerParameterName = isJokerAttempt
6326
- ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
6327
- : undefined;
6328
- if (isJokerAttempt && !jokerParameterName) {
6329
- throw new UnexpectedError(spaceTrim$1((block) => `
6330
- Joker not found in attempt ${attemptIndex}
6331
-
6332
- ${block(pipelineIdentification)}
6333
- `));
6882
+ /**
6883
+ * Function checkExpectations will check if the expectations on given value are met
6884
+ *
6885
+ * Note: There are two similar functions:
6886
+ * - `checkExpectations` which throws an error if the expectations are not met
6887
+ * - `isPassingExpectations` which returns a boolean
6888
+ *
6889
+ * @throws {ExpectError} if the expectations are not met
6890
+ * @returns {void} Nothing
6891
+ *
6892
+ * @private internal function of `createPipelineExecutor`
6893
+ */
6894
+ function checkExpectations(expectations, value) {
6895
+ for (const [unit, { max, min }] of Object.entries(expectations)) {
6896
+ const amount = CountUtils[unit.toUpperCase()](value);
6897
+ if (min && amount < min) {
6898
+ throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6899
+ } /* not else */
6900
+ if (max && amount > max) {
6901
+ throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6902
+ }
6334
6903
  }
6335
- return {
6336
- attemptIndex,
6337
- isJokerAttempt,
6338
- jokerParameterName,
6339
- };
6340
6904
  }
6905
+ // TODO: [💝] Unite object for expecting amount and format
6906
+ // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6907
+ // Note: [💝] and [🤠] are interconnected together
6908
+
6341
6909
  /**
6342
- * Clears the per-attempt result slots while preserving cumulative failure history.
6910
+ * Validates a prompt result against expectations and format requirements.
6911
+ * This function provides a common abstraction for result validation that can be used
6912
+ * by both execution logic and caching logic to ensure consistency.
6913
+ *
6914
+ * Note: [🔂] This function is idempotent.
6915
+ *
6916
+ * @param options - The validation options including result string, expectations, and format
6917
+ * @returns Validation result with processed string and validity status
6918
+ *
6919
+ * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6343
6920
  */
6344
- function resetAttemptExecutionState($ongoingTaskResult) {
6345
- $ongoingTaskResult.$result = null;
6346
- $ongoingTaskResult.$resultString = null;
6347
- $ongoingTaskResult.$expectError = null;
6921
+ function validatePromptResult(options) {
6922
+ const { resultString, expectations, format } = options;
6923
+ let processedResultString = resultString;
6924
+ let validationError;
6925
+ try {
6926
+ // TODO: [💝] Unite object for expecting amount and format
6927
+ if (format) {
6928
+ if (format === 'JSON') {
6929
+ if (!isValidJsonString(processedResultString)) {
6930
+ // TODO: [🏢] Do more universally via `FormatParser`
6931
+ try {
6932
+ processedResultString = extractJsonBlock(processedResultString);
6933
+ }
6934
+ catch (error) {
6935
+ keepUnused(error);
6936
+ throw new ExpectError(spaceTrim$1((block) => `
6937
+ Expected valid JSON string
6938
+
6939
+ The expected JSON text:
6940
+ ${block(processedResultString)}
6941
+ `));
6942
+ }
6943
+ }
6944
+ }
6945
+ else {
6946
+ throw new UnexpectedError(`Unknown format "${format}"`);
6947
+ }
6948
+ }
6949
+ // TODO: [💝] Unite object for expecting amount and format
6950
+ if (expectations) {
6951
+ checkExpectations(expectations, processedResultString);
6952
+ }
6953
+ return {
6954
+ isValid: true,
6955
+ processedResultString,
6956
+ };
6957
+ }
6958
+ catch (error) {
6959
+ if (error instanceof ExpectError) {
6960
+ validationError = error;
6961
+ }
6962
+ else {
6963
+ // Re-throw non-ExpectError errors (like UnexpectedError)
6964
+ throw error;
6965
+ }
6966
+ return {
6967
+ isValid: false,
6968
+ processedResultString,
6969
+ error: validationError,
6970
+ };
6971
+ }
6348
6972
  }
6973
+
6349
6974
  /**
6350
6975
  * Executes one loop iteration, from joker resolution or task execution through validation.
6976
+ *
6977
+ * @private function of `executeAttempts`
6351
6978
  */
6352
6979
  async function executeSingleAttempt(options) {
6353
6980
  const { attempt, options: executeAttemptsOptions, llmTools, $ongoingTaskResult } = options;
@@ -6662,11 +7289,15 @@ function validateAttemptResult(options) {
6662
7289
  // Update the result string in case format processing modified it (e.g., JSON extraction)
6663
7290
  $ongoingTaskResult.$resultString = validationResult.processedResultString;
6664
7291
  }
7292
+
6665
7293
  /**
6666
- * Stores one failed attempt and reports the expectation error upstream.
7294
+ * Stores one failed attempt, reports the expectation error, and throws the aggregated retry error after the final
7295
+ * regular attempt.
7296
+ *
7297
+ * @private function of `executeAttempts`
6667
7298
  */
6668
- function recordFailedAttempt(options) {
6669
- const { error, attemptIndex, onProgress, $ongoingTaskResult } = options;
7299
+ function handleAttemptFailure(options) {
7300
+ const { error, attemptIndex, maxAttempts, maxExecutionAttempts, onProgress, pipelineIdentification, $ongoingTaskResult, } = options;
6670
7301
  $ongoingTaskResult.$expectError = error;
6671
7302
  $ongoingTaskResult.$failedResults.push({
6672
7303
  attemptIndex,
@@ -6676,39 +7307,7 @@ function recordFailedAttempt(options) {
6676
7307
  onProgress({
6677
7308
  errors: [error],
6678
7309
  });
6679
- }
6680
- /**
6681
- * Appends the prompt execution report for prompt-task attempts.
6682
- */
6683
- function reportPromptExecution(options) {
6684
- const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
6685
- if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
6686
- return;
6687
- }
6688
- // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
6689
- // In that case we don’t want to make a report about it because it’s not a llm execution error
6690
- const executionPromptReport = {
6691
- prompt: {
6692
- ...$ongoingTaskResult.$prompt,
6693
- // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
6694
- },
6695
- result: $ongoingTaskResult.$result || undefined,
6696
- error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
6697
- };
6698
- $executionReport.promptExecutions.push(executionPromptReport);
6699
- if (logLlmCall) {
6700
- logLlmCall({
6701
- modelName: 'model' /* <- TODO: How to get model name from the report */,
6702
- report: executionPromptReport,
6703
- });
6704
- }
6705
- }
6706
- /**
6707
- * Throws the aggregated retry error after the last regular attempt fails expectations.
6708
- */
6709
- function throwIfFinalAttemptFailed(options) {
6710
- const { attemptIndex, maxAttempts, maxExecutionAttempts, pipelineIdentification, $ongoingTaskResult } = options;
6711
- if ($ongoingTaskResult.$expectError === null || attemptIndex !== maxAttempts - 1) {
7310
+ if (attemptIndex !== maxAttempts - 1) {
6712
7311
  return;
6713
7312
  }
6714
7313
  throw new PipelineExecutionError(spaceTrim$1((block) => {
@@ -6753,6 +7352,136 @@ function quoteMultilineText(text) {
6753
7352
  .map((line) => `> ${line}`)
6754
7353
  .join('\n');
6755
7354
  }
7355
+
7356
+ /**
7357
+ * Appends the prompt execution report for prompt-task attempts.
7358
+ *
7359
+ * @private function of `executeAttempts`
7360
+ */
7361
+ function reportPromptExecution(options) {
7362
+ const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
7363
+ if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
7364
+ return;
7365
+ }
7366
+ // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
7367
+ // In that case we don’t want to make a report about it because it’s not a llm execution error
7368
+ const executionPromptReport = {
7369
+ prompt: {
7370
+ ...$ongoingTaskResult.$prompt,
7371
+ // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
7372
+ },
7373
+ result: $ongoingTaskResult.$result || undefined,
7374
+ error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
7375
+ };
7376
+ $executionReport.promptExecutions.push(executionPromptReport);
7377
+ if (logLlmCall) {
7378
+ logLlmCall({
7379
+ modelName: 'model' /* <- TODO: How to get model name from the report */,
7380
+ report: executionPromptReport,
7381
+ });
7382
+ }
7383
+ }
7384
+
7385
+ /**
7386
+ * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
7387
+ * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
7388
+ * Throws errors if execution fails after all attempts.
7389
+ *
7390
+ * @param options - The options for execution, including task, parameters, pipeline, and configuration.
7391
+ * @returns The result string of the executed task.
7392
+ *
7393
+ * @private internal utility of `createPipelineExecutor`
7394
+ */
7395
+ async function executeAttempts(options) {
7396
+ const $ongoingTaskResult = createOngoingTaskResult();
7397
+ const llmTools = getSingleLlmExecutionTools(options.tools.llm);
7398
+ attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
7399
+ const attempt = createAttemptDescriptor({
7400
+ attemptIndex,
7401
+ jokerParameterNames: options.jokerParameterNames,
7402
+ pipelineIdentification: options.pipelineIdentification,
7403
+ });
7404
+ resetAttemptExecutionState($ongoingTaskResult);
7405
+ try {
7406
+ await executeSingleAttempt({
7407
+ attempt,
7408
+ options,
7409
+ llmTools,
7410
+ $ongoingTaskResult,
7411
+ });
7412
+ break attempts;
7413
+ }
7414
+ catch (error) {
7415
+ if (!(error instanceof ExpectError)) {
7416
+ throw error;
7417
+ }
7418
+ handleAttemptFailure({
7419
+ error,
7420
+ attemptIndex,
7421
+ maxAttempts: options.maxAttempts,
7422
+ maxExecutionAttempts: options.maxExecutionAttempts,
7423
+ onProgress: options.onProgress,
7424
+ pipelineIdentification: options.pipelineIdentification,
7425
+ $ongoingTaskResult,
7426
+ });
7427
+ }
7428
+ finally {
7429
+ reportPromptExecution({
7430
+ attempt,
7431
+ task: options.task,
7432
+ $executionReport: options.$executionReport,
7433
+ logLlmCall: options.logLlmCall,
7434
+ $ongoingTaskResult,
7435
+ });
7436
+ }
7437
+ }
7438
+ return getSuccessfulResultString({
7439
+ pipelineIdentification: options.pipelineIdentification,
7440
+ $ongoingTaskResult,
7441
+ });
7442
+ }
7443
+ /**
7444
+ * Creates mutable attempt state for one task execution lifecycle.
7445
+ */
7446
+ function createOngoingTaskResult() {
7447
+ return {
7448
+ $result: null,
7449
+ $resultString: null,
7450
+ $expectError: null,
7451
+ $scriptPipelineExecutionErrors: [],
7452
+ $failedResults: [],
7453
+ };
7454
+ }
7455
+ /**
7456
+ * Resolves the bookkeeping for one loop iteration, including joker lookup.
7457
+ */
7458
+ function createAttemptDescriptor(options) {
7459
+ const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
7460
+ const isJokerAttempt = attemptIndex < 0;
7461
+ const jokerParameterName = isJokerAttempt
7462
+ ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
7463
+ : undefined;
7464
+ if (isJokerAttempt && !jokerParameterName) {
7465
+ throw new UnexpectedError(spaceTrim$1((block) => `
7466
+ Joker not found in attempt ${attemptIndex}
7467
+
7468
+ ${block(pipelineIdentification)}
7469
+ `));
7470
+ }
7471
+ return {
7472
+ attemptIndex,
7473
+ isJokerAttempt,
7474
+ jokerParameterName,
7475
+ };
7476
+ }
7477
+ /**
7478
+ * Clears the per-attempt result slots while preserving cumulative failure history.
7479
+ */
7480
+ function resetAttemptExecutionState($ongoingTaskResult) {
7481
+ $ongoingTaskResult.$result = null;
7482
+ $ongoingTaskResult.$resultString = null;
7483
+ $ongoingTaskResult.$expectError = null;
7484
+ }
6756
7485
  /**
6757
7486
  * Returns the successful result string or raises an unexpected internal-state error.
6758
7487
  */