@promptbook/markdown-utils 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 +1775 -1046
  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 +1 -1
  107. package/umd/index.umd.js +1775 -1046
  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
@@ -23,7 +23,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
23
23
  * @generated
24
24
  * @see https://github.com/webgptorg/promptbook
25
25
  */
26
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-72';
26
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-79';
27
27
  /**
28
28
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
29
29
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -413,6 +413,111 @@ function checkChannelValue(channelName, value) {
413
413
  }
414
414
  }
415
415
 
416
+ /**
417
+ * Shared immutable channel storage and serialization helpers for `Color`.
418
+ *
419
+ * @private base class of Color
420
+ */
421
+ class ColorValue {
422
+ constructor(red, green, blue, alpha = 255) {
423
+ this.red = red;
424
+ this.green = green;
425
+ this.blue = blue;
426
+ this.alpha = alpha;
427
+ checkChannelValue('Red', red);
428
+ checkChannelValue('Green', green);
429
+ checkChannelValue('Blue', blue);
430
+ checkChannelValue('Alpha', alpha);
431
+ }
432
+ /**
433
+ * Shortcut for `red` property
434
+ * Number from 0 to 255
435
+ * @alias red
436
+ */
437
+ get r() {
438
+ return this.red;
439
+ }
440
+ /**
441
+ * Shortcut for `green` property
442
+ * Number from 0 to 255
443
+ * @alias green
444
+ */
445
+ get g() {
446
+ return this.green;
447
+ }
448
+ /**
449
+ * Shortcut for `blue` property
450
+ * Number from 0 to 255
451
+ * @alias blue
452
+ */
453
+ get b() {
454
+ return this.blue;
455
+ }
456
+ /**
457
+ * Shortcut for `alpha` property
458
+ * Number from 0 (transparent) to 255 (opaque)
459
+ * @alias alpha
460
+ */
461
+ get a() {
462
+ return this.alpha;
463
+ }
464
+ /**
465
+ * Shortcut for `alpha` property
466
+ * Number from 0 (transparent) to 255 (opaque)
467
+ * @alias alpha
468
+ */
469
+ get opacity() {
470
+ return this.alpha;
471
+ }
472
+ /**
473
+ * Shortcut for 1-`alpha` property
474
+ */
475
+ get transparency() {
476
+ return 255 - this.alpha;
477
+ }
478
+ clone() {
479
+ return take(this.createColor(this.red, this.green, this.blue, this.alpha));
480
+ }
481
+ toString() {
482
+ return this.toHex();
483
+ }
484
+ toHex() {
485
+ if (this.alpha === 255) {
486
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
487
+ .toString(16)
488
+ .padStart(2, '0')}`;
489
+ }
490
+ else {
491
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
492
+ .toString(16)
493
+ .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
494
+ }
495
+ }
496
+ toRgb() {
497
+ if (this.alpha === 255) {
498
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
499
+ }
500
+ else {
501
+ return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
502
+ }
503
+ }
504
+ toHsl() {
505
+ throw new Error(`Getting HSL is not implemented`);
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Checks if the given value is a valid hex color string
511
+ *
512
+ * @param value - value to check
513
+ * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
514
+ *
515
+ * @private function of Color
516
+ */
517
+ function isHexColorString(value) {
518
+ 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));
519
+ }
520
+
416
521
  /**
417
522
  * Constant for short hex lengths.
418
523
  */
@@ -624,16 +729,53 @@ function parseAlphaValue(value) {
624
729
 
625
730
  /**
626
731
  * Pattern matching hsl regex.
732
+ *
733
+ * @private function of Color
627
734
  */
628
735
  const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
629
736
  /**
630
737
  * Pattern matching RGB regex.
738
+ *
739
+ * @private function of Color
631
740
  */
632
741
  const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
633
742
  /**
634
743
  * Pattern matching rgba regex.
744
+ *
745
+ * @private function of Color
635
746
  */
636
747
  const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
748
+ /**
749
+ * Parses a supported color string into RGBA channels.
750
+ *
751
+ * @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`,...
752
+ * @returns RGBA channel values.
753
+ *
754
+ * @private function of Color
755
+ */
756
+ function parseColorString(color) {
757
+ const trimmed = color.trim();
758
+ const cssColor = CSS_COLORS[trimmed];
759
+ if (cssColor) {
760
+ return parseColorString(cssColor);
761
+ }
762
+ else if (isHexColorString(trimmed)) {
763
+ return parseHexColor(trimmed);
764
+ }
765
+ if (HSL_REGEX_PATTERN.test(trimmed)) {
766
+ return parseHslColor(trimmed);
767
+ }
768
+ else if (RGB_REGEX_PATTERN.test(trimmed)) {
769
+ return parseRgbColor(trimmed);
770
+ }
771
+ else if (RGBA_REGEX_PATTERN.test(trimmed)) {
772
+ return parseRgbaColor(trimmed);
773
+ }
774
+ else {
775
+ throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
776
+ }
777
+ }
778
+
637
779
  /**
638
780
  * Color object represents an RGB color with alpha channel
639
781
  *
@@ -641,7 +783,7 @@ const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.
641
783
  *
642
784
  * @public exported from `@promptbook/color`
643
785
  */
644
- class Color {
786
+ class Color extends ColorValue {
645
787
  /**
646
788
  * Creates a new Color instance from miscellaneous formats
647
789
  * - It can receive Color instance and just return the same instance
@@ -714,25 +856,7 @@ class Color {
714
856
  * @returns Color object
715
857
  */
716
858
  static fromString(color) {
717
- const trimmed = color.trim();
718
- if (CSS_COLORS[trimmed]) {
719
- return Color.fromString(CSS_COLORS[trimmed]);
720
- }
721
- else if (Color.isHexColorString(trimmed)) {
722
- return Color.fromHex(trimmed);
723
- }
724
- if (HSL_REGEX_PATTERN.test(trimmed)) {
725
- return Color.fromHsl(trimmed);
726
- }
727
- else if (RGB_REGEX_PATTERN.test(trimmed)) {
728
- return Color.fromRgbString(trimmed);
729
- }
730
- else if (RGBA_REGEX_PATTERN.test(trimmed)) {
731
- return Color.fromRgbaString(trimmed);
732
- }
733
- else {
734
- throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
735
- }
859
+ return Color.fromColorChannels(parseColorString(color));
736
860
  }
737
861
  /**
738
862
  * Gets common color
@@ -762,8 +886,7 @@ class Color {
762
886
  * @returns Color object
763
887
  */
764
888
  static fromHex(hex) {
765
- const { red, green, blue, alpha } = parseHexColor(hex);
766
- return take(new Color(red, green, blue, alpha));
889
+ return Color.fromColorChannels(parseHexColor(hex));
767
890
  }
768
891
  /**
769
892
  * Creates a new Color instance from color in hsl format
@@ -772,8 +895,7 @@ class Color {
772
895
  * @returns Color object
773
896
  */
774
897
  static fromHsl(hsl) {
775
- const { red, green, blue, alpha } = parseHslColor(hsl);
776
- return take(new Color(red, green, blue, alpha));
898
+ return Color.fromColorChannels(parseHslColor(hsl));
777
899
  }
778
900
  /**
779
901
  * Creates a new Color instance from color in rgb format
@@ -782,8 +904,7 @@ class Color {
782
904
  * @returns Color object
783
905
  */
784
906
  static fromRgbString(rgb) {
785
- const { red, green, blue, alpha } = parseRgbColor(rgb);
786
- return take(new Color(red, green, blue, alpha));
907
+ return Color.fromColorChannels(parseRgbColor(rgb));
787
908
  }
788
909
  /**
789
910
  * Creates a new Color instance from color in rbga format
@@ -792,8 +913,7 @@ class Color {
792
913
  * @returns Color object
793
914
  */
794
915
  static fromRgbaString(rgba) {
795
- const { red, green, blue, alpha } = parseRgbaColor(rgba);
796
- return take(new Color(red, green, blue, alpha));
916
+ return Color.fromColorChannels(parseRgbaColor(rgba));
797
917
  }
798
918
  /**
799
919
  * Creates a new Color for color channels values
@@ -805,7 +925,7 @@ class Color {
805
925
  * @returns Color object
806
926
  */
807
927
  static fromValues(red, green, blue, alpha = 255) {
808
- return take(new Color(red, green, blue, alpha));
928
+ return Color.fromColorChannels({ red, green, blue, alpha });
809
929
  }
810
930
  /**
811
931
  * Checks if the given value is a valid Color object.
@@ -838,8 +958,7 @@ class Color {
838
958
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
839
959
  */
840
960
  static isHexColorString(value) {
841
- return (typeof value === 'string' &&
842
- /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
961
+ return isHexColorString(value);
843
962
  }
844
963
  /**
845
964
  * Creates new Color object
@@ -852,89 +971,13 @@ class Color {
852
971
  * @param alpha number from 0 (transparent) to 255 (opaque)
853
972
  */
854
973
  constructor(red, green, blue, alpha = 255) {
855
- this.red = red;
856
- this.green = green;
857
- this.blue = blue;
858
- this.alpha = alpha;
859
- checkChannelValue('Red', red);
860
- checkChannelValue('Green', green);
861
- checkChannelValue('Blue', blue);
862
- checkChannelValue('Alpha', alpha);
863
- }
864
- /**
865
- * Shortcut for `red` property
866
- * Number from 0 to 255
867
- * @alias red
868
- */
869
- get r() {
870
- return this.red;
871
- }
872
- /**
873
- * Shortcut for `green` property
874
- * Number from 0 to 255
875
- * @alias green
876
- */
877
- get g() {
878
- return this.green;
879
- }
880
- /**
881
- * Shortcut for `blue` property
882
- * Number from 0 to 255
883
- * @alias blue
884
- */
885
- get b() {
886
- return this.blue;
887
- }
888
- /**
889
- * Shortcut for `alpha` property
890
- * Number from 0 (transparent) to 255 (opaque)
891
- * @alias alpha
892
- */
893
- get a() {
894
- return this.alpha;
895
- }
896
- /**
897
- * Shortcut for `alpha` property
898
- * Number from 0 (transparent) to 255 (opaque)
899
- * @alias alpha
900
- */
901
- get opacity() {
902
- return this.alpha;
903
- }
904
- /**
905
- * Shortcut for 1-`alpha` property
906
- */
907
- get transparency() {
908
- return 255 - this.alpha;
909
- }
910
- clone() {
911
- return take(new Color(this.red, this.green, this.blue, this.alpha));
912
- }
913
- toString() {
914
- return this.toHex();
915
- }
916
- toHex() {
917
- if (this.alpha === 255) {
918
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
919
- .toString(16)
920
- .padStart(2, '0')}`;
921
- }
922
- else {
923
- return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
924
- .toString(16)
925
- .padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
926
- }
974
+ super(red, green, blue, alpha);
927
975
  }
928
- toRgb() {
929
- if (this.alpha === 255) {
930
- return `rgb(${this.red}, ${this.green}, ${this.blue})`;
931
- }
932
- else {
933
- return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
934
- }
976
+ createColor(red, green, blue, alpha) {
977
+ return new Color(red, green, blue, alpha);
935
978
  }
936
- toHsl() {
937
- throw new Error(`Getting HSL is not implemented`);
979
+ static fromColorChannels({ red, green, blue, alpha }) {
980
+ return take(new Color(red, green, blue, alpha));
938
981
  }
939
982
  }
940
983
 
@@ -1691,6 +1734,60 @@ function validatePipelineString(pipelineString) {
1691
1734
  }
1692
1735
  // TODO: [🧠][🈴] Where is the best location for this file
1693
1736
 
1737
+ /**
1738
+ * Appends one markdown block to an existing markdown document.
1739
+ *
1740
+ * @private internal utility of `pipelineJsonToString`
1741
+ */
1742
+ function appendMarkdownBlock(pipelineString, markdownBlock) {
1743
+ return spaceTrim$1((block) => `
1744
+ ${block(pipelineString)}
1745
+
1746
+ ${block(markdownBlock)}
1747
+ `);
1748
+ }
1749
+
1750
+ /**
1751
+ * Collects pipeline-level commands in the existing serialization order.
1752
+ *
1753
+ * @private internal utility of `pipelineJsonToString`
1754
+ */
1755
+ function createPipelineCommands(pipelineJson) {
1756
+ const { pipelineUrl, bookVersion, parameters } = pipelineJson;
1757
+ const commands = [];
1758
+ if (pipelineUrl) {
1759
+ commands.push(`PIPELINE URL ${pipelineUrl}`);
1760
+ }
1761
+ if (bookVersion !== `undefined`) {
1762
+ commands.push(`BOOK VERSION ${bookVersion}`);
1763
+ }
1764
+ commands.push(...createParameterCommands(parameters, 'INPUT PARAMETER', ({ isInput }) => isInput));
1765
+ commands.push(...createParameterCommands(parameters, 'OUTPUT PARAMETER', ({ isOutput }) => isOutput));
1766
+ return commands;
1767
+ }
1768
+ /**
1769
+ * Builds one group of parameter commands while preserving the original parameter order.
1770
+ *
1771
+ * @private internal utility of `createPipelineCommands`
1772
+ */
1773
+ function createParameterCommands(parameters, commandPrefix, isIncluded) {
1774
+ return parameters
1775
+ .filter((parameter) => isIncluded(parameter))
1776
+ .map((parameter) => `${commandPrefix} ${parameterJsonToString(parameter)}`);
1777
+ }
1778
+ /**
1779
+ * Converts one parameter JSON declaration to the serialized inline form.
1780
+ *
1781
+ * @private internal utility of `createPipelineCommands`
1782
+ */
1783
+ function parameterJsonToString(parameterJson) {
1784
+ const { name, description } = parameterJson;
1785
+ if (!description) {
1786
+ return `{${name}}`;
1787
+ }
1788
+ return `{${name}} ${description}`;
1789
+ }
1790
+
1694
1791
  /**
1695
1792
  * Prettify the html code
1696
1793
  *
@@ -1705,141 +1802,211 @@ function prettifyMarkdown(content) {
1705
1802
  }
1706
1803
 
1707
1804
  /**
1708
- * Converts promptbook in JSON format to string format
1709
- *
1710
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1711
- * @param pipelineJson Promptbook in JSON format (.bookc)
1712
- * @returns Promptbook in string format (.book.md)
1805
+ * Creates the initial markdown heading and description of a pipeline.
1713
1806
  *
1714
- * @public exported from `@promptbook/core`
1807
+ * @private internal utility of `pipelineJsonToString`
1715
1808
  */
1716
- function pipelineJsonToString(pipelineJson) {
1717
- const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
1718
- let pipelineString = spaceTrim$1((block) => `
1809
+ function createPipelineIntroduction(pipelineJson) {
1810
+ const { title, description } = pipelineJson;
1811
+ const pipelineIntroduction = spaceTrim$1((block) => `
1719
1812
  # ${title}
1720
1813
 
1721
1814
  ${block(description || '')}
1722
1815
  `);
1723
- const commands = [];
1724
- if (pipelineUrl) {
1725
- commands.push(`PIPELINE URL ${pipelineUrl}`);
1726
- }
1727
- if (bookVersion !== `undefined`) {
1728
- commands.push(`BOOK VERSION ${bookVersion}`);
1729
- }
1730
1816
  // TODO: [main] !!5 This increases size of the bundle and is probably not necessary
1731
- pipelineString = prettifyMarkdown(pipelineString);
1732
- for (const parameter of parameters.filter(({ isInput }) => isInput)) {
1733
- commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
1817
+ return prettifyMarkdown(pipelineIntroduction);
1818
+ }
1819
+
1820
+ /**
1821
+ * Renders commands as markdown bullet items.
1822
+ *
1823
+ * @private internal utility of `pipelineJsonToString`
1824
+ */
1825
+ function stringifyCommands(commands) {
1826
+ return commands.map((command) => `- ${command}`).join('\n');
1827
+ }
1828
+
1829
+ /**
1830
+ * Collects all task-specific serialization details.
1831
+ *
1832
+ * @private internal utility of `pipelineJsonToString`
1833
+ */
1834
+ function createTaskSerialization(task) {
1835
+ const taskTypeSerialization = createTaskTypeSerialization(task);
1836
+ return {
1837
+ commands: [
1838
+ ...taskTypeSerialization.commands,
1839
+ ...createJokerCommands(task),
1840
+ ...createPostprocessingCommands(task),
1841
+ ...createExpectationCommands(task),
1842
+ ...createFormatCommands(task),
1843
+ ],
1844
+ contentLanguage: taskTypeSerialization.contentLanguage,
1845
+ };
1846
+ }
1847
+ /**
1848
+ * Collects commands and content language driven by the task type.
1849
+ *
1850
+ * @private internal utility of `createTaskSerialization`
1851
+ */
1852
+ function createTaskTypeSerialization(task) {
1853
+ if (task.taskType === 'PROMPT_TASK') {
1854
+ return {
1855
+ commands: createPromptTaskCommands(task),
1856
+ contentLanguage: 'text',
1857
+ };
1734
1858
  }
1735
- for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
1736
- commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
1859
+ if (task.taskType === 'SIMPLE_TASK') {
1860
+ return {
1861
+ commands: ['SIMPLE TEMPLATE'],
1862
+ contentLanguage: 'text',
1863
+ };
1737
1864
  }
1738
- pipelineString = spaceTrim$1((block) => `
1739
- ${block(pipelineString)}
1740
-
1741
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
1742
- `);
1743
- for (const task of tasks) {
1744
- const {
1745
- /* Note: Not using:> name, */
1746
- title, description,
1747
- /* Note: dependentParameterNames, */
1748
- jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
1749
- const commands = [];
1750
- let contentLanguage = 'text';
1751
- if (taskType === 'PROMPT_TASK') {
1752
- const { modelRequirements } = task;
1753
- const { modelName, modelVariant } = modelRequirements || {};
1754
- // Note: Do nothing, it is default
1755
- // commands.push(`PROMPT`);
1756
- if (modelVariant) {
1757
- commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1758
- }
1759
- if (modelName) {
1760
- commands.push(`MODEL NAME \`${modelName}\``);
1761
- }
1762
- }
1763
- else if (taskType === 'SIMPLE_TASK') {
1764
- commands.push(`SIMPLE TEMPLATE`);
1765
- // Note: Nothing special here
1766
- }
1767
- else if (taskType === 'SCRIPT_TASK') {
1768
- commands.push(`SCRIPT`);
1769
- if (task.contentLanguage) {
1770
- contentLanguage = task.contentLanguage;
1771
- }
1772
- else {
1773
- contentLanguage = '';
1774
- }
1775
- }
1776
- else if (taskType === 'DIALOG_TASK') {
1777
- commands.push(`DIALOG`);
1778
- // Note: Nothing special here
1779
- } // <- }else if([🅱]
1780
- if (jokers) {
1781
- for (const joker of jokers) {
1782
- commands.push(`JOKER {${joker}}`);
1783
- }
1784
- } /* not else */
1785
- if (postprocessing) {
1786
- for (const postprocessingFunctionName of postprocessing) {
1787
- commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
1788
- }
1789
- } /* not else */
1790
- if (expectations) {
1791
- for (const [unit, { min, max }] of Object.entries(expectations)) {
1792
- if (min === max) {
1793
- commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1794
- }
1795
- else {
1796
- if (min !== undefined) {
1797
- commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
1798
- } /* not else */
1799
- if (max !== undefined) {
1800
- commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
1801
- }
1802
- }
1803
- }
1804
- } /* not else */
1805
- if (format) {
1806
- if (format === 'JSON') {
1807
- // TODO: @deprecated remove
1808
- commands.push(`FORMAT JSON`);
1809
- }
1810
- } /* not else */
1811
- pipelineString = spaceTrim$1((block) => `
1812
- ${block(pipelineString)}
1865
+ if (task.taskType === 'SCRIPT_TASK') {
1866
+ return {
1867
+ commands: ['SCRIPT'],
1868
+ contentLanguage: task.contentLanguage || '',
1869
+ };
1870
+ }
1871
+ if (task.taskType === 'DIALOG_TASK') {
1872
+ return {
1873
+ commands: ['DIALOG'],
1874
+ contentLanguage: 'text',
1875
+ };
1876
+ }
1877
+ return {
1878
+ commands: [],
1879
+ contentLanguage: 'text',
1880
+ };
1881
+ }
1882
+ /**
1883
+ * Collects prompt-task-specific commands.
1884
+ *
1885
+ * @private internal utility of `createTaskSerialization`
1886
+ */
1887
+ function createPromptTaskCommands(task) {
1888
+ const { modelName, modelVariant } = task.modelRequirements || {};
1889
+ const commands = [];
1890
+ // Note: Do nothing, it is default
1891
+ // commands.push(`PROMPT`);
1892
+ if (modelVariant) {
1893
+ commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
1894
+ }
1895
+ if (modelName) {
1896
+ commands.push(`MODEL NAME \`${modelName}\``);
1897
+ }
1898
+ return commands;
1899
+ }
1900
+ /**
1901
+ * Collects joker commands.
1902
+ *
1903
+ * @private internal utility of `createTaskSerialization`
1904
+ */
1905
+ function createJokerCommands(task) {
1906
+ var _a;
1907
+ return ((_a = task.jokerParameterNames) === null || _a === void 0 ? void 0 : _a.map((joker) => `JOKER {${joker}}`)) || [];
1908
+ }
1909
+ /**
1910
+ * Collects postprocessing commands.
1911
+ *
1912
+ * @private internal utility of `createTaskSerialization`
1913
+ */
1914
+ function createPostprocessingCommands(task) {
1915
+ var _a;
1916
+ return (((_a = task.postprocessingFunctionNames) === null || _a === void 0 ? void 0 : _a.map((postprocessingFunctionName) => `POSTPROCESSING \`${postprocessingFunctionName}\``)) || []);
1917
+ }
1918
+ /**
1919
+ * Collects expectation commands.
1920
+ *
1921
+ * @private internal utility of `createTaskSerialization`
1922
+ */
1923
+ function createExpectationCommands(task) {
1924
+ if (!task.expectations) {
1925
+ return [];
1926
+ }
1927
+ return Object.entries(task.expectations).flatMap(([unit, expectation]) => createExpectationCommandsForUnit(unit, expectation.min, expectation.max));
1928
+ }
1929
+ /**
1930
+ * Collects expectation commands for a single unit.
1931
+ *
1932
+ * @private internal utility of `createTaskSerialization`
1933
+ */
1934
+ function createExpectationCommandsForUnit(unit, min, max) {
1935
+ if (min === max) {
1936
+ return [`EXPECT EXACTLY ${min} ${formatExpectationUnit(unit, min)}`];
1937
+ }
1938
+ const commands = [];
1939
+ if (min !== undefined) {
1940
+ commands.push(`EXPECT MIN ${min} ${formatExpectationUnit(unit, min)}`);
1941
+ }
1942
+ if (max !== undefined) {
1943
+ commands.push(`EXPECT MAX ${max} ${formatExpectationUnit(unit, max)}`);
1944
+ }
1945
+ return commands;
1946
+ }
1947
+ /**
1948
+ * Formats the expectation unit exactly as the legacy serializer does.
1949
+ *
1950
+ * @private internal utility of `createTaskSerialization`
1951
+ */
1952
+ function formatExpectationUnit(unit, amount) {
1953
+ return capitalize(unit + (amount > 1 ? 's' : ''));
1954
+ }
1955
+ /**
1956
+ * Collects format commands.
1957
+ *
1958
+ * @private internal utility of `createTaskSerialization`
1959
+ */
1960
+ function createFormatCommands(task) {
1961
+ if (task.format === 'JSON') {
1962
+ // TODO: @deprecated remove
1963
+ return ['FORMAT JSON'];
1964
+ }
1965
+ return [];
1966
+ }
1813
1967
 
1814
- ## ${title}
1968
+ /**
1969
+ * Stringifies one task section of the pipeline.
1970
+ *
1971
+ * @private internal utility of `pipelineJsonToString`
1972
+ */
1973
+ function stringifyTask(task) {
1974
+ const { title, description, content, resultingParameterName } = task;
1975
+ const { commands, contentLanguage } = createTaskSerialization(task);
1976
+ return spaceTrim$1((block) => `
1977
+ ## ${title}
1815
1978
 
1816
- ${block(description || '')}
1979
+ ${block(description || '')}
1817
1980
 
1818
- ${block(commands.map((command) => `- ${command}`).join('\n'))}
1981
+ ${block(stringifyCommands(commands))}
1819
1982
 
1820
- \`\`\`${contentLanguage}
1821
- ${block(spaceTrim$1(content))}
1822
- \`\`\`
1983
+ \`\`\`${contentLanguage}
1984
+ ${block(spaceTrim$1(content))}
1985
+ \`\`\`
1823
1986
 
1824
- \`-> {${resultingParameterName}}\`
1825
- `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1826
- // <- TODO: [main] !!3 Escape
1827
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1828
- }
1829
- return validatePipelineString(pipelineString);
1987
+ \`-> {${resultingParameterName}}\`
1988
+ `); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1989
+ // <- TODO: [main] !!3 Escape
1990
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1830
1991
  }
1992
+
1831
1993
  /**
1832
- * Handles task parameter Json to string.
1994
+ * Converts promptbook in JSON format to string format
1833
1995
  *
1834
- * @private internal utility of `pipelineJsonToString`
1996
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1997
+ * @param pipelineJson Promptbook in JSON format (.bookc)
1998
+ * @returns Promptbook in string format (.book.md)
1999
+ *
2000
+ * @public exported from `@promptbook/core`
1835
2001
  */
1836
- function taskParameterJsonToString(taskParameterJson) {
1837
- const { name, description } = taskParameterJson;
1838
- let parameterString = `{${name}}`;
1839
- if (description) {
1840
- parameterString = `${parameterString} ${description}`;
2002
+ function pipelineJsonToString(pipelineJson) {
2003
+ let pipelineString = createPipelineIntroduction(pipelineJson);
2004
+ const pipelineCommands = createPipelineCommands(pipelineJson);
2005
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyCommands(pipelineCommands));
2006
+ for (const task of pipelineJson.tasks) {
2007
+ pipelineString = appendMarkdownBlock(pipelineString, stringifyTask(task));
1841
2008
  }
1842
- return parameterString;
2009
+ return validatePipelineString(pipelineString);
1843
2010
  }
1844
2011
  // TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
1845
2012
  // TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
@@ -1911,120 +2078,183 @@ function $deepFreeze(objectValue) {
1911
2078
  * @public exported from `@promptbook/utils`
1912
2079
  */
1913
2080
  function checkSerializableAsJson(options) {
1914
- const { value, name, message } = options;
2081
+ checkSerializableValue(options);
2082
+ }
2083
+ // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2084
+ // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2085
+ // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2086
+ /**
2087
+ * Checks one value and dispatches to the appropriate specialized validator.
2088
+ *
2089
+ * @private function of `checkSerializableAsJson`
2090
+ */
2091
+ function checkSerializableValue(options) {
2092
+ const { value } = options;
2093
+ if (isSerializablePrimitive(value)) {
2094
+ return;
2095
+ }
1915
2096
  if (value === undefined) {
1916
- throw new UnexpectedError(`${name} is undefined`);
2097
+ throw new UnexpectedError(`${options.name} is undefined`);
1917
2098
  }
1918
- else if (value === null) {
1919
- return;
2099
+ if (typeof value === 'symbol') {
2100
+ throw new UnexpectedError(`${options.name} is symbol`);
1920
2101
  }
1921
- else if (typeof value === 'boolean') {
1922
- return;
2102
+ if (typeof value === 'function') {
2103
+ throw new UnexpectedError(`${options.name} is function`);
1923
2104
  }
1924
- else if (typeof value === 'number' && !isNaN(value)) {
2105
+ if (Array.isArray(value)) {
2106
+ checkSerializableArray(options, value);
1925
2107
  return;
1926
2108
  }
1927
- else if (typeof value === 'string') {
2109
+ if (value !== null && typeof value === 'object') {
2110
+ checkSerializableObject(options, value);
1928
2111
  return;
1929
2112
  }
1930
- else if (typeof value === 'symbol') {
1931
- throw new UnexpectedError(`${name} is symbol`);
1932
- }
1933
- else if (typeof value === 'function') {
1934
- throw new UnexpectedError(`${name} is function`);
1935
- }
1936
- else if (typeof value === 'object' && Array.isArray(value)) {
1937
- for (let i = 0; i < value.length; i++) {
1938
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1939
- }
2113
+ throwUnknownTypeError(options);
2114
+ }
2115
+ /**
2116
+ * Checks the primitive values that are directly JSON serializable.
2117
+ *
2118
+ * @private function of `checkSerializableAsJson`
2119
+ */
2120
+ function isSerializablePrimitive(value) {
2121
+ return (value === null ||
2122
+ typeof value === 'boolean' ||
2123
+ (typeof value === 'number' && !isNaN(value)) ||
2124
+ typeof value === 'string');
2125
+ }
2126
+ /**
2127
+ * Recursively checks JSON array items.
2128
+ *
2129
+ * @private function of `checkSerializableAsJson`
2130
+ */
2131
+ function checkSerializableArray(context, arrayValue) {
2132
+ for (let index = 0; index < arrayValue.length; index++) {
2133
+ checkSerializableAsJson({
2134
+ ...context,
2135
+ name: `${context.name}[${index}]`,
2136
+ value: arrayValue[index],
2137
+ });
1940
2138
  }
1941
- else if (typeof value === 'object') {
1942
- if (value instanceof Date) {
1943
- throw new UnexpectedError(spaceTrim$1((block) => `
1944
- \`${name}\` is Date
2139
+ }
2140
+ /**
2141
+ * Checks object-like values and dispatches special unsupported built-ins.
2142
+ *
2143
+ * @private function of `checkSerializableAsJson`
2144
+ */
2145
+ function checkSerializableObject(context, objectValue) {
2146
+ checkUnsupportedObjectType(context, objectValue);
2147
+ checkSerializableObjectEntries(context, objectValue);
2148
+ assertJsonStringificationSucceeds(context, objectValue);
2149
+ }
2150
+ /**
2151
+ * Rejects built-in objects that must be converted before JSON serialization.
2152
+ *
2153
+ * @private function of `checkSerializableAsJson`
2154
+ */
2155
+ function checkUnsupportedObjectType(context, objectValue) {
2156
+ if (objectValue instanceof Date) {
2157
+ throw new UnexpectedError(spaceTrim$1((block) => `
2158
+ \`${context.name}\` is Date
1945
2159
 
1946
- Use \`string_date_iso8601\` instead
2160
+ Use \`string_date_iso8601\` instead
1947
2161
 
1948
- Additional message for \`${name}\`:
1949
- ${block(message || '(nothing)')}
1950
- `));
1951
- }
1952
- else if (value instanceof Map) {
1953
- throw new UnexpectedError(`${name} is Map`);
1954
- }
1955
- else if (value instanceof Set) {
1956
- throw new UnexpectedError(`${name} is Set`);
1957
- }
1958
- else if (value instanceof RegExp) {
1959
- throw new UnexpectedError(`${name} is RegExp`);
1960
- }
1961
- else if (value instanceof Error) {
1962
- throw new UnexpectedError(spaceTrim$1((block) => `
1963
- \`${name}\` is unserialized Error
2162
+ Additional message for \`${context.name}\`:
2163
+ ${block(context.message || '(nothing)')}
2164
+ `));
2165
+ }
2166
+ if (objectValue instanceof Map) {
2167
+ throw new UnexpectedError(`${context.name} is Map`);
2168
+ }
2169
+ if (objectValue instanceof Set) {
2170
+ throw new UnexpectedError(`${context.name} is Set`);
2171
+ }
2172
+ if (objectValue instanceof RegExp) {
2173
+ throw new UnexpectedError(`${context.name} is RegExp`);
2174
+ }
2175
+ if (objectValue instanceof Error) {
2176
+ throw new UnexpectedError(spaceTrim$1((block) => `
2177
+ \`${context.name}\` is unserialized Error
1964
2178
 
1965
- Use function \`serializeError\`
2179
+ Use function \`serializeError\`
1966
2180
 
1967
- Additional message for \`${name}\`:
1968
- ${block(message || '(nothing)')}
2181
+ Additional message for \`${context.name}\`:
2182
+ ${block(context.message || '(nothing)')}
1969
2183
 
1970
- `));
2184
+ `));
2185
+ }
2186
+ }
2187
+ /**
2188
+ * Recursively checks object properties while preserving omitted `undefined` keys.
2189
+ *
2190
+ * @private function of `checkSerializableAsJson`
2191
+ */
2192
+ function checkSerializableObjectEntries(context, objectValue) {
2193
+ for (const [subName, subValue] of Object.entries(objectValue)) {
2194
+ if (subValue === undefined) {
2195
+ // Note: undefined in object is serializable - it is just omitted
2196
+ continue;
1971
2197
  }
1972
- else {
1973
- for (const [subName, subValue] of Object.entries(value)) {
1974
- if (subValue === undefined) {
1975
- // Note: undefined in object is serializable - it is just omitted
1976
- continue;
1977
- }
1978
- checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
1979
- }
1980
- try {
1981
- JSON.stringify(value); // <- TODO: [0]
1982
- }
1983
- catch (error) {
1984
- assertsError(error);
1985
- throw new UnexpectedError(spaceTrim$1((block) => `
1986
- \`${name}\` is not serializable
2198
+ checkSerializableAsJson({
2199
+ ...context,
2200
+ name: `${context.name}.${subName}`,
2201
+ value: subValue,
2202
+ });
2203
+ }
2204
+ }
2205
+ /**
2206
+ * Uses `JSON.stringify` as the final guard for cases like circular references.
2207
+ *
2208
+ * @private function of `checkSerializableAsJson`
2209
+ */
2210
+ function assertJsonStringificationSucceeds(context, objectValue) {
2211
+ try {
2212
+ JSON.stringify(objectValue); // <- TODO: [0]
2213
+ }
2214
+ catch (error) {
2215
+ assertsError(error);
2216
+ throw new UnexpectedError(spaceTrim$1((block) => `
2217
+ \`${context.name}\` is not serializable
1987
2218
 
1988
- ${block(error.stack || error.message)}
2219
+ ${block(error.stack || error.message)}
1989
2220
 
1990
- Additional message for \`${name}\`:
1991
- ${block(message || '(nothing)')}
1992
- `));
2221
+ Additional message for \`${context.name}\`:
2222
+ ${block(context.message || '(nothing)')}
2223
+ `));
2224
+ }
2225
+ /*
2226
+ TODO: [0] Is there some more elegant way to check circular references?
2227
+ const seen = new Set();
2228
+ const stack = [{ value }];
2229
+ while (stack.length > 0) {
2230
+ const { value } = stack.pop()!;
2231
+ if (typeof value === 'object' && value !== null) {
2232
+ if (seen.has(value)) {
2233
+ throw new UnexpectedError(`${name} has circular reference`);
1993
2234
  }
1994
- /*
1995
- TODO: [0] Is there some more elegant way to check circular references?
1996
- const seen = new Set();
1997
- const stack = [{ value }];
1998
- while (stack.length > 0) {
1999
- const { value } = stack.pop()!;
2000
- if (typeof value === 'object' && value !== null) {
2001
- if (seen.has(value)) {
2002
- throw new UnexpectedError(`${name} has circular reference`);
2003
- }
2004
- seen.add(value);
2005
- if (Array.isArray(value)) {
2006
- stack.push(...value.map((value) => ({ value })));
2007
- } else {
2008
- stack.push(...Object.values(value).map((value) => ({ value })));
2009
- }
2010
- }
2235
+ seen.add(value);
2236
+ if (Array.isArray(value)) {
2237
+ stack.push(...value.map((value) => ({ value })));
2238
+ } else {
2239
+ stack.push(...Object.values(value).map((value) => ({ value })));
2011
2240
  }
2012
- */
2013
- return;
2014
2241
  }
2015
2242
  }
2016
- else {
2017
- throw new UnexpectedError(spaceTrim$1((block) => `
2018
- \`${name}\` is unknown type
2243
+ */
2244
+ }
2245
+ /**
2246
+ * Throws the fallback error for unsupported value types like `bigint` and `NaN`.
2247
+ *
2248
+ * @private function of `checkSerializableAsJson`
2249
+ */
2250
+ function throwUnknownTypeError(context) {
2251
+ throw new UnexpectedError(spaceTrim$1((block) => `
2252
+ \`${context.name}\` is unknown type
2019
2253
 
2020
- Additional message for \`${name}\`:
2021
- ${block(message || '(nothing)')}
2022
- `));
2023
- }
2254
+ Additional message for \`${context.name}\`:
2255
+ ${block(context.message || '(nothing)')}
2256
+ `));
2024
2257
  }
2025
- // TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
2026
- // TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
2027
- // Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
2028
2258
 
2029
2259
  /**
2030
2260
  * Creates a deep clone of the given object
@@ -2279,233 +2509,518 @@ function validatePipeline(pipeline) {
2279
2509
  */
2280
2510
  function validatePipeline_InnerFunction(pipeline) {
2281
2511
  // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
2282
- const pipelineIdentification = (() => {
2283
- // Note: This is a 😐 implementation of [🚞]
2284
- const _ = [];
2285
- if (pipeline.sourceFile !== undefined) {
2286
- _.push(`File: ${pipeline.sourceFile}`);
2287
- }
2288
- if (pipeline.pipelineUrl !== undefined) {
2289
- _.push(`Url: ${pipeline.pipelineUrl}`);
2512
+ const context = createPipelineValidationContext(pipeline);
2513
+ validatePipelineMetadata(context);
2514
+ validatePipelineCollectionsStructure(context);
2515
+ validatePipelineParameters(context);
2516
+ validatePipelineTasks(context);
2517
+ validatePipelineDependencyResolution(context);
2518
+ // Note: Check that formfactor is corresponding to the pipeline interface
2519
+ // TODO: !!6 Implement this
2520
+ // pipeline.formfactorName
2521
+ }
2522
+ /**
2523
+ * Creates the shared validation context for one pipeline.
2524
+ *
2525
+ * @private internal utility of `validatePipeline`
2526
+ */
2527
+ function createPipelineValidationContext(pipeline) {
2528
+ return {
2529
+ pipeline,
2530
+ pipelineIdentification: getPipelineIdentification(pipeline),
2531
+ };
2532
+ }
2533
+ /**
2534
+ * Builds a short file/url identification block for validation errors.
2535
+ *
2536
+ * @private internal utility of `validatePipeline`
2537
+ */
2538
+ function getPipelineIdentification(pipeline) {
2539
+ // Note: This is a 😐 implementation of [🚞]
2540
+ const pipelineIdentificationParts = [];
2541
+ if (pipeline.sourceFile !== undefined) {
2542
+ pipelineIdentificationParts.push(`File: ${pipeline.sourceFile}`);
2543
+ }
2544
+ if (pipeline.pipelineUrl !== undefined) {
2545
+ pipelineIdentificationParts.push(`Url: ${pipeline.pipelineUrl}`);
2546
+ }
2547
+ return pipelineIdentificationParts.join('\n');
2548
+ }
2549
+ /**
2550
+ * Validates pipeline-level metadata fields.
2551
+ *
2552
+ * @private internal step of `validatePipeline`
2553
+ */
2554
+ function validatePipelineMetadata({ pipeline, pipelineIdentification }) {
2555
+ validatePipelineUrl(pipeline, pipelineIdentification);
2556
+ validatePipelineBookVersion(pipeline, pipelineIdentification);
2557
+ }
2558
+ /**
2559
+ * Validates that the expected top-level collections have array structure.
2560
+ *
2561
+ * @private internal step of `validatePipeline`
2562
+ */
2563
+ function validatePipelineCollectionsStructure({ pipeline, pipelineIdentification }) {
2564
+ validatePipelineParametersCollection(pipeline, pipelineIdentification);
2565
+ validatePipelineTasksCollection(pipeline, pipelineIdentification);
2566
+ }
2567
+ /**
2568
+ * Validates all pipeline parameter declarations.
2569
+ *
2570
+ * @private internal step of `validatePipeline`
2571
+ */
2572
+ function validatePipelineParameters({ pipeline, pipelineIdentification }) {
2573
+ for (const parameter of pipeline.parameters) {
2574
+ validatePipelineParameter(parameter, pipeline, pipelineIdentification);
2575
+ }
2576
+ }
2577
+ /**
2578
+ * Validates all pipeline tasks and their per-task invariants.
2579
+ *
2580
+ * @private internal step of `validatePipeline`
2581
+ */
2582
+ function validatePipelineTasks({ pipeline, pipelineIdentification }) {
2583
+ // Note: All input parameters are defined - so that they can be used as result of some task
2584
+ const definedParameters = createInitiallyDefinedParameters(pipeline);
2585
+ for (const task of pipeline.tasks) {
2586
+ validatePipelineTask(task, definedParameters, pipelineIdentification);
2587
+ }
2588
+ }
2589
+ /**
2590
+ * Validates that task dependencies can be resolved without cycles or missing definitions.
2591
+ *
2592
+ * @private internal step of `validatePipeline`
2593
+ */
2594
+ function validatePipelineDependencyResolution({ pipeline, pipelineIdentification }) {
2595
+ let dependencyResolutionState = createInitialDependencyResolutionState(pipeline);
2596
+ let loopLimit = LOOP_LIMIT;
2597
+ while (hasUnresolvedTasks(dependencyResolutionState)) {
2598
+ if (loopLimit-- < 0) {
2599
+ throw createDependencyResolutionLoopLimitError(pipelineIdentification);
2290
2600
  }
2291
- return _.join('\n');
2292
- })();
2293
- if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
2294
- // <- Note: [🚲]
2295
- throw new PipelineLogicError(spaceTrim$1((block) => `
2296
- Invalid promptbook URL "${pipeline.pipelineUrl}"
2297
-
2298
- ${block(pipelineIdentification)}
2299
- `));
2601
+ dependencyResolutionState = resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification);
2300
2602
  }
2301
- if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
2302
- // <- Note: [🚲]
2303
- throw new PipelineLogicError(spaceTrim$1((block) => `
2304
- Invalid Promptbook Version "${pipeline.bookVersion}"
2603
+ }
2604
+ /**
2605
+ * Validates one pipeline parameter declaration.
2606
+ *
2607
+ * @private internal step of `validatePipeline`
2608
+ */
2609
+ function validatePipelineParameter(parameter, pipeline, pipelineIdentification) {
2610
+ validateParameterDirection(parameter, pipelineIdentification);
2611
+ validateParameterUsage(parameter, pipeline, pipelineIdentification);
2612
+ validateParameterDefinition(parameter, pipeline, pipelineIdentification);
2613
+ }
2614
+ /**
2615
+ * Validates one pipeline task and its invariants.
2616
+ *
2617
+ * @private internal step of `validatePipeline`
2618
+ */
2619
+ function validatePipelineTask(task, definedParameters, pipelineIdentification) {
2620
+ validateTaskResultingParameter(task, definedParameters, pipelineIdentification);
2621
+ validateTaskJokers(task, pipelineIdentification);
2622
+ validateTaskExpectations(task, pipelineIdentification);
2623
+ }
2624
+ /**
2625
+ * Validates the pipeline URL, when present.
2626
+ *
2627
+ * @private internal utility of `validatePipeline`
2628
+ */
2629
+ function validatePipelineUrl(pipeline, pipelineIdentification) {
2630
+ if (pipeline.pipelineUrl === undefined || isValidPipelineUrl(pipeline.pipelineUrl)) {
2631
+ return;
2632
+ }
2633
+ // <- Note: [🚲]
2634
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2635
+ Invalid promptbook URL "${pipeline.pipelineUrl}"
2305
2636
 
2306
- ${block(pipelineIdentification)}
2307
- `));
2637
+ ${block(pipelineIdentification)}
2638
+ `));
2639
+ }
2640
+ /**
2641
+ * Validates the Promptbook version, when present.
2642
+ *
2643
+ * @private internal utility of `validatePipeline`
2644
+ */
2645
+ function validatePipelineBookVersion(pipeline, pipelineIdentification) {
2646
+ if (pipeline.bookVersion === undefined || isValidPromptbookVersion(pipeline.bookVersion)) {
2647
+ return;
2308
2648
  }
2649
+ // <- Note: [🚲]
2650
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2651
+ Invalid Promptbook Version "${pipeline.bookVersion}"
2652
+
2653
+ ${block(pipelineIdentification)}
2654
+ `));
2655
+ }
2656
+ /**
2657
+ * Validates that `pipeline.parameters` is an array.
2658
+ *
2659
+ * @private internal utility of `validatePipeline`
2660
+ */
2661
+ function validatePipelineParametersCollection(pipeline, pipelineIdentification) {
2309
2662
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2310
- if (!Array.isArray(pipeline.parameters)) {
2311
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2312
- throw new ParseError(spaceTrim$1((block) => `
2313
- Pipeline is valid JSON but with wrong structure
2663
+ if (Array.isArray(pipeline.parameters)) {
2664
+ return;
2665
+ }
2666
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2667
+ throw new ParseError(spaceTrim$1((block) => `
2668
+ Pipeline is valid JSON but with wrong structure
2314
2669
 
2315
- \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2670
+ \`PipelineJson.parameters\` expected to be an array, but got ${typeof pipeline.parameters}
2316
2671
 
2317
- ${block(pipelineIdentification)}
2318
- `));
2319
- }
2672
+ ${block(pipelineIdentification)}
2673
+ `));
2674
+ }
2675
+ /**
2676
+ * Validates that `pipeline.tasks` is an array.
2677
+ *
2678
+ * @private internal utility of `validatePipeline`
2679
+ */
2680
+ function validatePipelineTasksCollection(pipeline, pipelineIdentification) {
2320
2681
  // TODO: [🧠] Maybe do here some proper JSON-schema / ZOD checking
2321
- if (!Array.isArray(pipeline.tasks)) {
2322
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2323
- throw new ParseError(spaceTrim$1((block) => `
2324
- Pipeline is valid JSON but with wrong structure
2682
+ if (Array.isArray(pipeline.tasks)) {
2683
+ return;
2684
+ }
2685
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
2686
+ throw new ParseError(spaceTrim$1((block) => `
2687
+ Pipeline is valid JSON but with wrong structure
2325
2688
 
2326
- \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2689
+ \`PipelineJson.tasks\` expected to be an array, but got ${typeof pipeline.tasks}
2327
2690
 
2328
- ${block(pipelineIdentification)}
2329
- `));
2691
+ ${block(pipelineIdentification)}
2692
+ `));
2693
+ }
2694
+ /**
2695
+ * Validates that one parameter does not declare incompatible directions.
2696
+ *
2697
+ * @private internal utility of `validatePipeline`
2698
+ */
2699
+ function validateParameterDirection(parameter, pipelineIdentification) {
2700
+ if (!parameter.isInput || !parameter.isOutput) {
2701
+ return;
2330
2702
  }
2331
- /*
2332
- TODO: [🧠][🅾] Should be empty pipeline valid or not
2333
- // Note: Check that pipeline has some tasks
2334
- if (pipeline.tasks.length === 0) {
2335
- throw new PipelineLogicError(
2336
- spaceTrim(
2337
- (block) => `
2338
- Pipeline must have at least one task
2703
+ const parameterName = parameter.name;
2704
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2339
2705
 
2340
- ${block(pipelineIdentification)}
2341
- `,
2342
- ),
2343
- );
2344
- }
2345
- */
2346
- // Note: Check each parameter individually
2347
- for (const parameter of pipeline.parameters) {
2348
- if (parameter.isInput && parameter.isOutput) {
2349
- throw new PipelineLogicError(spaceTrim$1((block) => `
2706
+ Parameter \`{${parameterName}}\` can not be both input and output
2350
2707
 
2351
- Parameter \`{${parameter.name}}\` can not be both input and output
2708
+ ${block(pipelineIdentification)}
2709
+ `));
2710
+ }
2711
+ /**
2712
+ * Validates that one intermediate parameter is actually consumed by at least one task.
2713
+ *
2714
+ * @private internal utility of `validatePipeline`
2715
+ */
2716
+ function validateParameterUsage(parameter, pipeline, pipelineIdentification) {
2717
+ if (parameter.isInput || parameter.isOutput || isParameterUsedByAnyTask(parameter, pipeline.tasks)) {
2718
+ return;
2719
+ }
2720
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2721
+ Parameter \`{${parameter.name}}\` is created but not used
2352
2722
 
2353
- ${block(pipelineIdentification)}
2354
- `));
2355
- }
2356
- // Note: Testing that parameter is either intermediate or output BUT not created and unused
2357
- if (!parameter.isInput &&
2358
- !parameter.isOutput &&
2359
- !pipeline.tasks.some((task) => task.dependentParameterNames.includes(parameter.name))) {
2360
- throw new PipelineLogicError(spaceTrim$1((block) => `
2361
- Parameter \`{${parameter.name}}\` is created but not used
2723
+ You can declare {${parameter.name}} as output parameter by adding in the header:
2724
+ - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2362
2725
 
2363
- You can declare {${parameter.name}} as output parameter by adding in the header:
2364
- - OUTPUT PARAMETER \`{${parameter.name}}\` ${parameter.description || ''}
2726
+ ${block(pipelineIdentification)}
2365
2727
 
2366
- ${block(pipelineIdentification)}
2728
+ `));
2729
+ }
2730
+ /**
2731
+ * Validates that one non-input parameter is produced by at least one task.
2732
+ *
2733
+ * @private internal utility of `validatePipeline`
2734
+ */
2735
+ function validateParameterDefinition(parameter, pipeline, pipelineIdentification) {
2736
+ if (parameter.isInput || isParameterDefinedByAnyTask(parameter, pipeline.tasks)) {
2737
+ return;
2738
+ }
2739
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2740
+ Parameter \`{${parameter.name}}\` is declared but not defined
2367
2741
 
2368
- `));
2369
- }
2370
- // Note: Testing that parameter is either input or result of some task
2371
- if (!parameter.isInput && !pipeline.tasks.some((task) => task.resultingParameterName === parameter.name)) {
2372
- throw new PipelineLogicError(spaceTrim$1((block) => `
2373
- Parameter \`{${parameter.name}}\` is declared but not defined
2742
+ You can do one of these:
2743
+ 1) Remove declaration of \`{${parameter.name}}\`
2744
+ 2) Add task that results in \`-> {${parameter.name}}\`
2374
2745
 
2375
- You can do one of these:
2376
- 1) Remove declaration of \`{${parameter.name}}\`
2377
- 2) Add task that results in \`-> {${parameter.name}}\`
2746
+ ${block(pipelineIdentification)}
2747
+ `));
2748
+ }
2749
+ /**
2750
+ * Checks whether one parameter is consumed by at least one task dependency list.
2751
+ *
2752
+ * @private internal utility of `validatePipeline`
2753
+ */
2754
+ function isParameterUsedByAnyTask(parameter, tasks) {
2755
+ return tasks.some((task) => task.dependentParameterNames.includes(parameter.name));
2756
+ }
2757
+ /**
2758
+ * Checks whether one parameter is produced by at least one task.
2759
+ *
2760
+ * @private internal utility of `validatePipeline`
2761
+ */
2762
+ function isParameterDefinedByAnyTask(parameter, tasks) {
2763
+ return tasks.some((task) => task.resultingParameterName === parameter.name);
2764
+ }
2765
+ /**
2766
+ * Collects the parameter names that are already defined before task validation starts.
2767
+ *
2768
+ * @private internal utility of `validatePipeline`
2769
+ */
2770
+ function createInitiallyDefinedParameters(pipeline) {
2771
+ return new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2772
+ }
2773
+ /**
2774
+ * Validates one task result parameter declaration and marks it as defined.
2775
+ *
2776
+ * @private internal utility of `validatePipeline`
2777
+ */
2778
+ function validateTaskResultingParameter(task, definedParameters, pipelineIdentification) {
2779
+ if (definedParameters.has(task.resultingParameterName)) {
2780
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2781
+ Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2378
2782
 
2379
- ${block(pipelineIdentification)}
2380
- `));
2381
- }
2783
+ ${block(pipelineIdentification)}
2784
+ `));
2382
2785
  }
2383
- // Note: All input parameters are defined - so that they can be used as result of some task
2384
- const definedParameters = new Set(pipeline.parameters.filter(({ isInput }) => isInput).map(({ name }) => name));
2385
- // Note: Checking each task individually
2386
- for (const task of pipeline.tasks) {
2387
- if (definedParameters.has(task.resultingParameterName)) {
2388
- throw new PipelineLogicError(spaceTrim$1((block) => `
2389
- Parameter \`{${task.resultingParameterName}}\` is defined multiple times
2786
+ if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2787
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2788
+ Parameter name {${task.resultingParameterName}} is reserved, please use different name
2390
2789
 
2391
- ${block(pipelineIdentification)}
2392
- `));
2393
- }
2394
- if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
2395
- throw new PipelineLogicError(spaceTrim$1((block) => `
2396
- Parameter name {${task.resultingParameterName}} is reserved, please use different name
2790
+ ${block(pipelineIdentification)}
2791
+ `));
2792
+ }
2793
+ definedParameters.add(task.resultingParameterName);
2794
+ }
2795
+ /**
2796
+ * Validates joker parameters for one task.
2797
+ *
2798
+ * @private internal utility of `validatePipeline`
2799
+ */
2800
+ function validateTaskJokers(task, pipelineIdentification) {
2801
+ if (!hasTaskJokers(task)) {
2802
+ return;
2803
+ }
2804
+ validateTaskSupportsJokers(task, pipelineIdentification);
2805
+ validateTaskJokerDependencies(task, pipelineIdentification);
2806
+ }
2807
+ /**
2808
+ * Checks whether one task declares any joker parameters.
2809
+ *
2810
+ * @private internal utility of `validatePipeline`
2811
+ */
2812
+ function hasTaskJokers(task) {
2813
+ return !!task.jokerParameterNames && task.jokerParameterNames.length > 0;
2814
+ }
2815
+ /**
2816
+ * Validates that a task has the required supporting features when using jokers.
2817
+ *
2818
+ * @private internal utility of `validatePipeline`
2819
+ */
2820
+ function validateTaskSupportsJokers(task, pipelineIdentification) {
2821
+ if (task.format || task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2822
+ return;
2823
+ }
2824
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2825
+ Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2397
2826
 
2398
- ${block(pipelineIdentification)}
2399
- `));
2827
+ ${block(pipelineIdentification)}
2828
+ `));
2829
+ }
2830
+ /**
2831
+ * Validates that every joker parameter is also listed among task dependencies.
2832
+ *
2833
+ * @private internal utility of `validatePipeline`
2834
+ */
2835
+ function validateTaskJokerDependencies(task, pipelineIdentification) {
2836
+ for (const joker of task.jokerParameterNames) {
2837
+ if (task.dependentParameterNames.includes(joker)) {
2838
+ continue;
2400
2839
  }
2401
- definedParameters.add(task.resultingParameterName);
2402
- if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
2403
- if (!task.format &&
2404
- !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
2405
- throw new PipelineLogicError(spaceTrim$1((block) => `
2406
- Joker parameters are used for {${task.resultingParameterName}} but no expectations are defined
2407
-
2408
- ${block(pipelineIdentification)}
2409
- `));
2410
- }
2411
- for (const joker of task.jokerParameterNames) {
2412
- if (!task.dependentParameterNames.includes(joker)) {
2413
- throw new PipelineLogicError(spaceTrim$1((block) => `
2414
- Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2840
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2841
+ Parameter \`{${joker}}\` is used for {${task.resultingParameterName}} as joker but not in \`dependentParameterNames\`
2415
2842
 
2416
- ${block(pipelineIdentification)}
2417
- `));
2418
- }
2419
- }
2420
- }
2421
- if (task.expectations) {
2422
- for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2423
- if (min !== undefined && max !== undefined && min > max) {
2424
- throw new PipelineLogicError(spaceTrim$1((block) => `
2425
- Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2843
+ ${block(pipelineIdentification)}
2844
+ `));
2845
+ }
2846
+ }
2847
+ /**
2848
+ * Validates all expectation bounds configured on one task.
2849
+ *
2850
+ * @private internal utility of `validatePipeline`
2851
+ */
2852
+ function validateTaskExpectations(task, pipelineIdentification) {
2853
+ if (!task.expectations) {
2854
+ return;
2855
+ }
2856
+ for (const [unit, { min, max }] of Object.entries(task.expectations)) {
2857
+ validateTaskExpectationRange(unit, min, max, pipelineIdentification);
2858
+ validateTaskExpectationMin(unit, min, pipelineIdentification);
2859
+ validateTaskExpectationMax(unit, max, pipelineIdentification);
2860
+ }
2861
+ }
2862
+ /**
2863
+ * Validates the minimum and maximum expectation ordering for one unit.
2864
+ *
2865
+ * @private internal utility of `validatePipeline`
2866
+ */
2867
+ function validateTaskExpectationRange(unit, min, max, pipelineIdentification) {
2868
+ if (min === undefined || max === undefined || min <= max) {
2869
+ return;
2870
+ }
2871
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2872
+ Min expectation (=${min}) of ${unit} is higher than max expectation (=${max})
2426
2873
 
2427
- ${block(pipelineIdentification)}
2428
- `));
2429
- }
2430
- if (min !== undefined && min < 0) {
2431
- throw new PipelineLogicError(spaceTrim$1((block) => `
2432
- Min expectation of ${unit} must be zero or positive
2874
+ ${block(pipelineIdentification)}
2875
+ `));
2876
+ }
2877
+ /**
2878
+ * Validates the minimum expectation bound for one unit.
2879
+ *
2880
+ * @private internal utility of `validatePipeline`
2881
+ */
2882
+ function validateTaskExpectationMin(unit, min, pipelineIdentification) {
2883
+ if (min === undefined || min >= 0) {
2884
+ return;
2885
+ }
2886
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2887
+ Min expectation of ${unit} must be zero or positive
2433
2888
 
2434
- ${block(pipelineIdentification)}
2435
- `));
2436
- }
2437
- if (max !== undefined && max <= 0) {
2438
- throw new PipelineLogicError(spaceTrim$1((block) => `
2439
- Max expectation of ${unit} must be positive
2889
+ ${block(pipelineIdentification)}
2890
+ `));
2891
+ }
2892
+ /**
2893
+ * Validates the maximum expectation bound for one unit.
2894
+ *
2895
+ * @private internal utility of `validatePipeline`
2896
+ */
2897
+ function validateTaskExpectationMax(unit, max, pipelineIdentification) {
2898
+ if (max === undefined || max > 0) {
2899
+ return;
2900
+ }
2901
+ throw new PipelineLogicError(spaceTrim$1((block) => `
2902
+ Max expectation of ${unit} must be positive
2440
2903
 
2441
- ${block(pipelineIdentification)}
2442
- `));
2443
- }
2444
- }
2445
- }
2904
+ ${block(pipelineIdentification)}
2905
+ `));
2906
+ }
2907
+ /**
2908
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
2909
+ *
2910
+ * @private internal utility of `validatePipeline`
2911
+ */
2912
+ function createInitialDependencyResolutionState(pipeline) {
2913
+ return {
2914
+ resolvedParameterNames: createInitiallyResolvedParameterNames(pipeline),
2915
+ unresolvedTasks: [...pipeline.tasks],
2916
+ };
2917
+ }
2918
+ /**
2919
+ * Checks whether dependency resolution still has tasks left to process.
2920
+ *
2921
+ * @private internal utility of `validatePipeline`
2922
+ */
2923
+ function hasUnresolvedTasks({ unresolvedTasks }) {
2924
+ return unresolvedTasks.length > 0;
2925
+ }
2926
+ /**
2927
+ * Resolves the next batch of currently satisfiable tasks.
2928
+ *
2929
+ * @private internal utility of `validatePipeline`
2930
+ */
2931
+ function resolveNextDependencyResolutionState(dependencyResolutionState, pipelineIdentification) {
2932
+ const currentlyResolvedTasks = getCurrentlyResolvedTasks(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames);
2933
+ if (currentlyResolvedTasks.length === 0) {
2934
+ throw createUnresolvedTasksError(dependencyResolutionState.unresolvedTasks, dependencyResolutionState.resolvedParameterNames, pipelineIdentification);
2446
2935
  }
2447
- // Note: Detect circular dependencies
2448
- let resovedParameters = pipeline.parameters
2936
+ return {
2937
+ resolvedParameterNames: appendResolvedTaskParameterNames(dependencyResolutionState.resolvedParameterNames, currentlyResolvedTasks),
2938
+ unresolvedTasks: dependencyResolutionState.unresolvedTasks.filter((task) => !currentlyResolvedTasks.includes(task)),
2939
+ };
2940
+ }
2941
+ /**
2942
+ * Collects the parameter names that are already resolvable before dependency traversal starts.
2943
+ *
2944
+ * @private internal utility of `validatePipeline`
2945
+ */
2946
+ function createInitiallyResolvedParameterNames(pipeline) {
2947
+ let resolvedParameterNames = pipeline.parameters
2449
2948
  .filter(({ isInput }) => isInput)
2450
2949
  .map(({ name }) => name);
2451
- // Note: All reserved parameters are resolved
2452
2950
  for (const reservedParameterName of RESERVED_PARAMETER_NAMES) {
2453
- resovedParameters = [...resovedParameters, reservedParameterName];
2951
+ resolvedParameterNames = [...resolvedParameterNames, reservedParameterName];
2454
2952
  }
2455
- let unresovedTasks = [...pipeline.tasks];
2456
- let loopLimit = LOOP_LIMIT;
2457
- while (unresovedTasks.length > 0) {
2458
- if (loopLimit-- < 0) {
2459
- // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2460
- throw new UnexpectedError(spaceTrim$1((block) => `
2461
- Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2953
+ return resolvedParameterNames;
2954
+ }
2955
+ /**
2956
+ * Adds newly resolved task outputs to the resolved parameter list.
2957
+ *
2958
+ * @private internal utility of `validatePipeline`
2959
+ */
2960
+ function appendResolvedTaskParameterNames(resolvedParameterNames, currentlyResolvedTasks) {
2961
+ return [
2962
+ ...resolvedParameterNames,
2963
+ ...currentlyResolvedTasks.map(({ resultingParameterName }) => resultingParameterName),
2964
+ ];
2965
+ }
2966
+ /**
2967
+ * Selects tasks whose dependencies are already resolved.
2968
+ *
2969
+ * @private internal utility of `validatePipeline`
2970
+ */
2971
+ function getCurrentlyResolvedTasks(unresolvedTasks, resolvedParameterNames) {
2972
+ return unresolvedTasks.filter((task) => task.dependentParameterNames.every((name) => resolvedParameterNames.includes(name)));
2973
+ }
2974
+ /**
2975
+ * Creates the unexpected loop-limit error for dependency resolution.
2976
+ *
2977
+ * @private internal utility of `validatePipeline`
2978
+ */
2979
+ function createDependencyResolutionLoopLimitError(pipelineIdentification) {
2980
+ // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
2981
+ return new UnexpectedError(spaceTrim$1((block) => `
2982
+ Loop limit reached during detection of circular dependencies in \`validatePipeline\`
2462
2983
 
2463
- ${block(pipelineIdentification)}
2464
- `));
2465
- }
2466
- const currentlyResovedTasks = unresovedTasks.filter((task) => task.dependentParameterNames.every((name) => resovedParameters.includes(name)));
2467
- if (currentlyResovedTasks.length === 0) {
2468
- throw new PipelineLogicError(
2469
- // TODO: [🐎] DRY
2470
- spaceTrim$1((block) => `
2984
+ ${block(pipelineIdentification)}
2985
+ `));
2986
+ }
2987
+ /**
2988
+ * Creates the detailed error for unresolved or circular task dependencies.
2989
+ *
2990
+ * @private internal utility of `validatePipeline`
2991
+ */
2992
+ function createUnresolvedTasksError(unresolvedTasks, resolvedParameterNames, pipelineIdentification) {
2993
+ return new PipelineLogicError(
2994
+ // TODO: [🐎] DRY
2995
+ spaceTrim$1((block) => `
2471
2996
 
2472
- Can not resolve some parameters:
2473
- Either you are using a parameter that is not defined, or there are some circular dependencies.
2997
+ Can not resolve some parameters:
2998
+ Either you are using a parameter that is not defined, or there are some circular dependencies.
2474
2999
 
2475
- ${block(pipelineIdentification)}
3000
+ ${block(pipelineIdentification)}
2476
3001
 
2477
- **Can not resolve:**
2478
- ${block(unresovedTasks
2479
- .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
2480
- .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
2481
- .join(' and ')}`)
2482
- .join('\n'))}
3002
+ **Can not resolve:**
3003
+ ${block(unresolvedTasks
3004
+ .map(({ resultingParameterName, dependentParameterNames }) => `- Parameter \`{${resultingParameterName}}\` which depends on ${dependentParameterNames
3005
+ .map((dependentParameterName) => `\`{${dependentParameterName}}\``)
3006
+ .join(' and ')}`)
3007
+ .join('\n'))}
2483
3008
 
2484
- **Resolved:**
2485
- ${block(resovedParameters
2486
- .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
2487
- .map((name) => `- Parameter \`{${name}}\``)
2488
- .join('\n'))}
3009
+ **Resolved:**
3010
+ ${block(resolvedParameterNames
3011
+ .filter((name) => !RESERVED_PARAMETER_NAMES.includes(name))
3012
+ .map((name) => `- Parameter \`{${name}}\``)
3013
+ .join('\n'))}
2489
3014
 
2490
3015
 
2491
- **Reserved (which are available):**
2492
- ${block(resovedParameters
2493
- .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
2494
- .map((name) => `- Parameter \`{${name}}\``)
2495
- .join('\n'))}
3016
+ **Reserved (which are available):**
3017
+ ${block(resolvedParameterNames
3018
+ .filter((name) => RESERVED_PARAMETER_NAMES.includes(name))
3019
+ .map((name) => `- Parameter \`{${name}}\``)
3020
+ .join('\n'))}
2496
3021
 
2497
3022
 
2498
- `));
2499
- }
2500
- resovedParameters = [
2501
- ...resovedParameters,
2502
- ...currentlyResovedTasks.map(({ resultingParameterName }) => resultingParameterName),
2503
- ];
2504
- unresovedTasks = unresovedTasks.filter((task) => !currentlyResovedTasks.includes(task));
2505
- }
2506
- // Note: Check that formfactor is corresponding to the pipeline interface
2507
- // TODO: !!6 Implement this
2508
- // pipeline.formfactorName
3023
+ `));
2509
3024
  }
2510
3025
  /**
2511
3026
  * TODO: [🧞‍♀️] Do not allow joker + foreach
@@ -3227,70 +3742,281 @@ function assertsTaskSuccessful(executionResult) {
3227
3742
  // TODO: [🧠] Can this return type be better typed than void
3228
3743
 
3229
3744
  /**
3230
- * Helper to create a new task
3745
+ * Resolves the short task summary shown in the UI.
3746
+ *
3747
+ * @private internal helper function of `ExecutionTask`
3748
+ */
3749
+ function resolveTaskTldr(options) {
3750
+ const { customTldr } = options;
3751
+ if (customTldr) {
3752
+ return customTldr;
3753
+ }
3754
+ return {
3755
+ percent: resolveTaskPercent(options),
3756
+ message: `${resolveTaskMessage(options)} (!!!fallback)`,
3757
+ };
3758
+ }
3759
+ /**
3760
+ * Resolves the best progress percentage for the current task state.
3761
+ *
3762
+ * @private internal helper function of `ExecutionTask`
3763
+ */
3764
+ function resolveTaskPercent(options) {
3765
+ const explicitPercent = getExplicitTaskPercent(options.currentValue);
3766
+ if (typeof explicitPercent === 'number') {
3767
+ return normalizeTaskPercent(explicitPercent);
3768
+ }
3769
+ return normalizeTaskPercent(calculateSimulatedTaskPercent(options));
3770
+ }
3771
+ /**
3772
+ * Picks a directly reported progress percentage from the task result snapshot.
3773
+ *
3774
+ * @private internal helper function of `ExecutionTask`
3775
+ */
3776
+ function getExplicitTaskPercent(currentValue) {
3777
+ var _a, _b, _c, _d, _e, _f;
3778
+ 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);
3779
+ }
3780
+ /**
3781
+ * Simulates progress when the task result does not expose an explicit percentage.
3782
+ *
3783
+ * @private internal helper function of `ExecutionTask`
3784
+ */
3785
+ function calculateSimulatedTaskPercent(options) {
3786
+ const { currentValue, status, createdAt } = options;
3787
+ const elapsedMs = new Date().getTime() - createdAt.getTime();
3788
+ const timeProgress = Math.min(elapsedMs / DEFAULT_TASK_SIMULATED_DURATION_MS, 1);
3789
+ const { subtaskCount, completedSubtasks } = summarizeTaskSubtasks(currentValue);
3790
+ if (status === 'FINISHED') {
3791
+ return 1;
3792
+ }
3793
+ if (status === 'ERROR') {
3794
+ return 0;
3795
+ }
3796
+ return Math.min(completedSubtasks / subtaskCount + (1 / subtaskCount) * timeProgress, 1);
3797
+ }
3798
+ /**
3799
+ * Counts total and completed subtasks used by the fallback progress simulation.
3800
+ *
3801
+ * @private internal helper function of `ExecutionTask`
3802
+ */
3803
+ function summarizeTaskSubtasks(currentValue) {
3804
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks)) {
3805
+ return { subtaskCount: 1, completedSubtasks: 0 };
3806
+ }
3807
+ return {
3808
+ subtaskCount: currentValue.subtasks.length || 1,
3809
+ completedSubtasks: currentValue.subtasks.filter(isTaskSubtaskCompleted).length,
3810
+ };
3811
+ }
3812
+ /**
3813
+ * Tells whether a task subtask is already finished.
3814
+ *
3815
+ * @private internal helper function of `ExecutionTask`
3816
+ */
3817
+ function isTaskSubtaskCompleted(subtask) {
3818
+ return subtask.done || subtask.completed || false;
3819
+ }
3820
+ /**
3821
+ * Normalizes a progress percentage into the expected `0..1` range.
3822
+ *
3823
+ * @private internal helper function of `ExecutionTask`
3824
+ */
3825
+ function normalizeTaskPercent(percentRaw) {
3826
+ let percent = Number(percentRaw) || 0;
3827
+ if (percent < 0) {
3828
+ percent = 0;
3829
+ }
3830
+ if (percent > 1) {
3831
+ percent = 1;
3832
+ }
3833
+ return percent;
3834
+ }
3835
+ /**
3836
+ * Resolves the best human-readable status message for the current task state.
3837
+ *
3838
+ * @private internal helper function of `ExecutionTask`
3839
+ */
3840
+ function resolveTaskMessage(options) {
3841
+ return (getCurrentValueMessage(options.currentValue) ||
3842
+ getCurrentSubtaskMessage(options.currentValue) ||
3843
+ getLatestIssueMessage(options.errors, 'Error') ||
3844
+ getLatestIssueMessage(options.warnings, 'Warning') ||
3845
+ getStatusMessage(options.status));
3846
+ }
3847
+ /**
3848
+ * Picks a message already reported by the current task result snapshot.
3849
+ *
3850
+ * @private internal helper function of `ExecutionTask`
3851
+ */
3852
+ function getCurrentValueMessage(currentValue) {
3853
+ var _a, _b, _c, _d;
3854
+ 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;
3855
+ }
3856
+ /**
3857
+ * Builds a fallback message from the first unfinished subtask title.
3858
+ *
3859
+ * @private internal helper function of `ExecutionTask`
3860
+ */
3861
+ function getCurrentSubtaskMessage(currentValue) {
3862
+ if (!Array.isArray(currentValue === null || currentValue === void 0 ? void 0 : currentValue.subtasks) || currentValue.subtasks.length === 0) {
3863
+ return undefined;
3864
+ }
3865
+ const currentSubtask = currentValue.subtasks.find((subtask) => !isTaskSubtaskCompleted(subtask));
3866
+ if (!(currentSubtask === null || currentSubtask === void 0 ? void 0 : currentSubtask.title)) {
3867
+ return undefined;
3868
+ }
3869
+ return `Working on ${currentSubtask.title}`;
3870
+ }
3871
+ /**
3872
+ * Picks the latest error or warning message, with the legacy generic fallback label.
3873
+ *
3874
+ * @private internal helper function of `ExecutionTask`
3875
+ */
3876
+ function getLatestIssueMessage(issues, fallbackMessage) {
3877
+ if (issues.length === 0) {
3878
+ return undefined;
3879
+ }
3880
+ return issues[issues.length - 1].message || fallbackMessage;
3881
+ }
3882
+ /**
3883
+ * Builds the final status-based fallback message.
3884
+ *
3885
+ * @private internal helper function of `ExecutionTask`
3886
+ */
3887
+ function getStatusMessage(status) {
3888
+ if (status === 'FINISHED') {
3889
+ return 'Finished';
3890
+ }
3891
+ if (status === 'ERROR') {
3892
+ return 'Error';
3893
+ }
3894
+ return 'Running';
3895
+ }
3896
+
3897
+ /**
3898
+ * Creates the initial mutable state for a task.
3231
3899
  *
3232
3900
  * @private internal helper function
3233
3901
  */
3234
- function createTask(options) {
3235
- const { taskType, taskProcessCallback } = options;
3236
- let { title } = options;
3237
- // TODO: [🐙] DRY
3238
- const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
3239
- let status = 'RUNNING';
3240
- const createdAt = new Date();
3241
- let updatedAt = createdAt;
3242
- const errors = [];
3243
- const warnings = [];
3244
- const llmCalls = [];
3245
- let currentValue = {};
3246
- let customTldr = null;
3247
- const partialResultSubject = new Subject();
3248
- // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
3249
- const finalResultPromise = /* not await */ taskProcessCallback((newOngoingResult) => {
3902
+ function createTaskState(title, createdAt) {
3903
+ return {
3904
+ title,
3905
+ status: 'RUNNING',
3906
+ updatedAt: createdAt,
3907
+ errors: [],
3908
+ warnings: [],
3909
+ llmCalls: [],
3910
+ currentValue: {},
3911
+ customTldr: null,
3912
+ };
3913
+ }
3914
+ /**
3915
+ * Creates the partial-result updater passed into the task process callback.
3916
+ *
3917
+ * @private internal helper function
3918
+ */
3919
+ function createOngoingResultUpdater(taskState, partialResultSubject) {
3920
+ return (newOngoingResult) => {
3250
3921
  if (newOngoingResult.title) {
3251
- title = newOngoingResult.title;
3922
+ taskState.title = newOngoingResult.title;
3252
3923
  }
3253
- updatedAt = new Date();
3254
- Object.assign(currentValue, newOngoingResult);
3924
+ taskState.updatedAt = new Date();
3925
+ Object.assign(taskState.currentValue, newOngoingResult);
3255
3926
  // <- TODO: assign deep
3256
3927
  partialResultSubject.next(newOngoingResult);
3257
- }, (tldrInfo) => {
3258
- customTldr = tldrInfo;
3259
- updatedAt = new Date();
3260
- }, (llmCall) => {
3261
- llmCalls.push(llmCall);
3262
- updatedAt = new Date();
3263
- });
3928
+ };
3929
+ }
3930
+ /**
3931
+ * Creates the custom-TLDR updater passed into the task process callback.
3932
+ *
3933
+ * @private internal helper function
3934
+ */
3935
+ function createTldrUpdater(taskState) {
3936
+ return (tldrInfo) => {
3937
+ taskState.customTldr = tldrInfo;
3938
+ taskState.updatedAt = new Date();
3939
+ };
3940
+ }
3941
+ /**
3942
+ * Creates the LLM call logger passed into the task process callback.
3943
+ *
3944
+ * @private internal helper function
3945
+ */
3946
+ function createLlmCallLogger(taskState) {
3947
+ return (llmCall) => {
3948
+ taskState.llmCalls.push(llmCall);
3949
+ taskState.updatedAt = new Date();
3950
+ };
3951
+ }
3952
+ /**
3953
+ * Wires the task promise into the observable/error lifecycle.
3954
+ *
3955
+ * @private internal helper function
3956
+ */
3957
+ function settleTaskPromise(finalResultPromise, taskState, partialResultSubject) {
3264
3958
  finalResultPromise
3265
3959
  .catch((error) => {
3266
- errors.push(error);
3960
+ taskState.errors.push(error);
3267
3961
  partialResultSubject.error(error);
3268
3962
  })
3269
3963
  .then((executionResult) => {
3270
3964
  if (executionResult) {
3271
3965
  try {
3272
- updatedAt = new Date();
3273
- errors.push(...executionResult.errors);
3274
- warnings.push(...executionResult.warnings);
3275
- // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3276
- // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3277
- // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3278
- // And delete `ExecutionTask.currentValue.preparedPipeline`
3279
- assertsTaskSuccessful(executionResult);
3280
- status = 'FINISHED';
3281
- currentValue = jsonStringsToJsons(executionResult);
3282
- // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3283
- partialResultSubject.next(executionResult);
3966
+ finalizeTaskResult(executionResult, taskState, partialResultSubject);
3284
3967
  }
3285
3968
  catch (error) {
3286
- assertsError(error);
3287
- status = 'ERROR';
3288
- errors.push(error);
3289
- partialResultSubject.error(error);
3969
+ failTaskResult(error, taskState, partialResultSubject);
3290
3970
  }
3291
3971
  }
3292
3972
  partialResultSubject.complete();
3293
3973
  });
3974
+ }
3975
+ /**
3976
+ * Applies the final successful task result into the mutable task state.
3977
+ *
3978
+ * @private internal helper function
3979
+ */
3980
+ function finalizeTaskResult(executionResult, taskState, partialResultSubject) {
3981
+ taskState.updatedAt = new Date();
3982
+ taskState.errors.push(...executionResult.errors);
3983
+ taskState.warnings.push(...executionResult.warnings);
3984
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
3985
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
3986
+ // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
3987
+ // And delete `ExecutionTask.currentValue.preparedPipeline`
3988
+ assertsTaskSuccessful(executionResult);
3989
+ taskState.status = 'FINISHED';
3990
+ taskState.currentValue = jsonStringsToJsons(executionResult);
3991
+ // <- TODO: [🧠] Is this a good idea to convert JSON strins to JSONs?
3992
+ partialResultSubject.next(executionResult);
3993
+ }
3994
+ /**
3995
+ * Records a final-result failure after the task promise itself resolved.
3996
+ *
3997
+ * @private internal helper function
3998
+ */
3999
+ function failTaskResult(error, taskState, partialResultSubject) {
4000
+ assertsError(error);
4001
+ taskState.status = 'ERROR';
4002
+ taskState.errors.push(error);
4003
+ partialResultSubject.error(error);
4004
+ }
4005
+ /**
4006
+ * Helper to create a new task
4007
+ *
4008
+ * @private internal helper function
4009
+ */
4010
+ function createTask(options) {
4011
+ const { taskType, title, taskProcessCallback } = options;
4012
+ // TODO: [🐙] DRY
4013
+ const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
4014
+ const createdAt = new Date();
4015
+ const taskState = createTaskState(title, createdAt);
4016
+ const partialResultSubject = new Subject();
4017
+ // <- Note: Not using `BehaviorSubject` because on error we can't access the last value
4018
+ const finalResultPromise = /* not await */ taskProcessCallback(createOngoingResultUpdater(taskState, partialResultSubject), createTldrUpdater(taskState), createLlmCallLogger(taskState));
4019
+ settleTaskPromise(finalResultPromise, taskState, partialResultSubject);
3294
4020
  async function asPromise(options) {
3295
4021
  const { isCrashedOnError = true } = options || {};
3296
4022
  const finalResult = await finalResultPromise;
@@ -3306,91 +4032,29 @@ function createTask(options) {
3306
4032
  return PROMPTBOOK_ENGINE_VERSION;
3307
4033
  },
3308
4034
  get title() {
3309
- return title;
4035
+ return taskState.title;
3310
4036
  // <- Note: [1] These must be getters to allow changing the value in the future
3311
4037
  },
3312
4038
  get status() {
3313
- return status;
4039
+ return taskState.status;
3314
4040
  // <- Note: [1] --||--
3315
4041
  },
3316
4042
  get tldr() {
3317
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
3318
- // Use custom tldr if available
3319
- if (customTldr) {
3320
- return customTldr;
3321
- }
3322
- // Fallback to default implementation
3323
- const cv = currentValue;
3324
- // If explicit percent is provided, use it
3325
- let percentRaw = (_f = (_d = (_b = (_a = cv === null || cv === void 0 ? void 0 : cv.tldr) === null || _a === void 0 ? void 0 : _a.percent) !== null && _b !== void 0 ? _b : (_c = cv === null || cv === void 0 ? void 0 : cv.usage) === null || _c === void 0 ? void 0 : _c.percent) !== null && _d !== void 0 ? _d : (_e = cv === null || cv === void 0 ? void 0 : cv.progress) === null || _e === void 0 ? void 0 : _e.percent) !== null && _f !== void 0 ? _f : cv === null || cv === void 0 ? void 0 : cv.percent;
3326
- // Simulate progress if not provided
3327
- if (typeof percentRaw !== 'number') {
3328
- // Simulate progress: evenly split across subtasks, based on elapsed time
3329
- const now = new Date();
3330
- const elapsedMs = now.getTime() - createdAt.getTime();
3331
- const totalMs = DEFAULT_TASK_SIMULATED_DURATION_MS;
3332
- // If subtasks are defined, split progress evenly
3333
- const subtaskCount = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) ? cv.subtasks.length : 1;
3334
- const completedSubtasks = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks)
3335
- ? cv.subtasks.filter((s) => s.done || s.completed).length
3336
- : 0;
3337
- // Progress from completed subtasks
3338
- const subtaskProgress = subtaskCount > 0 ? completedSubtasks / subtaskCount : 0;
3339
- // Progress from elapsed time for current subtask
3340
- const timeProgress = Math.min(elapsedMs / totalMs, 1);
3341
- // Combine: completed subtasks + time progress for current subtask
3342
- percentRaw = Math.min(subtaskProgress + (1 / subtaskCount) * timeProgress, 1);
3343
- if (status === 'FINISHED')
3344
- percentRaw = 1;
3345
- if (status === 'ERROR')
3346
- percentRaw = 0;
3347
- }
3348
- // Clamp to [0,1]
3349
- let percent = Number(percentRaw) || 0;
3350
- if (percent < 0)
3351
- percent = 0;
3352
- if (percent > 1)
3353
- percent = 1;
3354
- // Build a short message: prefer explicit tldr.message, then common summary/message fields, then errors/warnings, then status
3355
- const messageFromResult = (_k = (_j = (_h = (_g = cv === null || cv === void 0 ? void 0 : cv.tldr) === null || _g === void 0 ? void 0 : _g.message) !== null && _h !== void 0 ? _h : cv === null || cv === void 0 ? void 0 : cv.message) !== null && _j !== void 0 ? _j : cv === null || cv === void 0 ? void 0 : cv.summary) !== null && _k !== void 0 ? _k : cv === null || cv === void 0 ? void 0 : cv.statusMessage;
3356
- let message = messageFromResult;
3357
- if (!message) {
3358
- // If subtasks, show current subtask
3359
- if (Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) && cv.subtasks.length > 0) {
3360
- const current = cv.subtasks.find((s) => !s.done && !s.completed);
3361
- if (current && current.title) {
3362
- message = `Working on ${current.title}`;
3363
- }
3364
- }
3365
- if (!message) {
3366
- if (errors.length) {
3367
- message = errors[errors.length - 1].message || 'Error';
3368
- }
3369
- else if (warnings.length) {
3370
- message = warnings[warnings.length - 1].message || 'Warning';
3371
- }
3372
- else if (status === 'FINISHED') {
3373
- message = 'Finished';
3374
- }
3375
- else if (status === 'ERROR') {
3376
- message = 'Error';
3377
- }
3378
- else {
3379
- message = 'Running';
3380
- }
3381
- }
3382
- }
3383
- return {
3384
- percent: percent,
3385
- message: message + ' (!!!fallback)',
3386
- };
4043
+ return resolveTaskTldr({
4044
+ customTldr: taskState.customTldr,
4045
+ currentValue: taskState.currentValue,
4046
+ status: taskState.status,
4047
+ createdAt,
4048
+ errors: taskState.errors,
4049
+ warnings: taskState.warnings,
4050
+ });
3387
4051
  },
3388
4052
  get createdAt() {
3389
4053
  return createdAt;
3390
4054
  // <- Note: [1] --||--
3391
4055
  },
3392
4056
  get updatedAt() {
3393
- return updatedAt;
4057
+ return taskState.updatedAt;
3394
4058
  // <- Note: [1] --||--
3395
4059
  },
3396
4060
  asPromise,
@@ -3398,19 +4062,19 @@ function createTask(options) {
3398
4062
  return partialResultSubject.asObservable();
3399
4063
  },
3400
4064
  get errors() {
3401
- return errors;
4065
+ return taskState.errors;
3402
4066
  // <- Note: [1] --||--
3403
4067
  },
3404
4068
  get warnings() {
3405
- return warnings;
4069
+ return taskState.warnings;
3406
4070
  // <- Note: [1] --||--
3407
4071
  },
3408
4072
  get llmCalls() {
3409
- return [...llmCalls, { foo: '!!! bar' }];
4073
+ return [...taskState.llmCalls, { foo: '!!! bar' }];
3410
4074
  // <- Note: [1] --||--
3411
4075
  },
3412
4076
  get currentValue() {
3413
- return currentValue;
4077
+ return taskState.currentValue;
3414
4078
  // <- Note: [1] --||--
3415
4079
  },
3416
4080
  };
@@ -4724,210 +5388,275 @@ const promptbookFetch = async (urlOrRequest, init) => {
4724
5388
  * @public exported from `@promptbook/core`
4725
5389
  */
4726
5390
  async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
5391
+ const { knowledgeSourceContent } = knowledgeSource;
5392
+ const name = knowledgeSource.name || knowledgeSourceContentToName(knowledgeSourceContent);
5393
+ const { rootDirname = null, isVerbose = DEFAULT_IS_VERBOSE } = options || {};
5394
+ if (isValidUrl(knowledgeSourceContent)) {
5395
+ return makeUrlKnowledgeSourceHandler(knowledgeSourceContent, name, tools, options, isVerbose);
5396
+ }
5397
+ if (isValidFilePath(knowledgeSourceContent)) {
5398
+ return makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose);
5399
+ }
5400
+ return makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose);
5401
+ }
5402
+ /**
5403
+ * Creates a source handler for URL-based knowledge.
5404
+ *
5405
+ * @private internal utility of `makeKnowledgeSourceHandler`
5406
+ */
5407
+ async function makeUrlKnowledgeSourceHandler(url, name, tools, options, isVerbose) {
4727
5408
  var _a;
4728
5409
  const { fetch = promptbookFetch } = tools;
4729
- const { knowledgeSourceContent } = knowledgeSource;
4730
- let { name } = knowledgeSource;
4731
- const { rootDirname = null,
4732
- // <- TODO: process.cwd() if running in Node.js
4733
- isVerbose = DEFAULT_IS_VERBOSE, } = options || {};
4734
- if (!name) {
4735
- name = knowledgeSourceContentToName(knowledgeSourceContent);
5410
+ if (isVerbose) {
5411
+ console.info(`📄 [1] "${name}" is available at "${url}"`);
4736
5412
  }
4737
- if (isValidUrl(knowledgeSourceContent)) {
4738
- const url = knowledgeSourceContent;
4739
- if (isVerbose) {
4740
- console.info(`📄 [1] "${name}" is available at "${url}"`);
4741
- }
4742
- const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
4743
- const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
4744
- if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
4745
- if (isVerbose) {
4746
- console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
4747
- }
4748
- return {
4749
- source: name,
4750
- filename: null,
4751
- url,
4752
- mimeType,
4753
- /*
4754
- TODO: [🥽]
4755
- > async asBlob() {
4756
- > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
4757
- > const content = await response.blob();
4758
- > return content;
4759
- > },
4760
- */
4761
- async asJson() {
4762
- // TODO: [👨🏻‍🤝‍👨🏻]
4763
- const content = await response.json();
4764
- return content;
4765
- },
4766
- async asText() {
4767
- // TODO: [👨🏻‍🤝‍👨🏻]
4768
- const content = await response.text();
4769
- return content;
4770
- },
4771
- };
4772
- }
4773
- const basename = url.split('/').pop() || titleToName(url);
4774
- const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
4775
- // <- TODO: [🥬] Encapsulate sha256 to some private utility function
4776
- const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
4777
- const filepath = join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
4778
- // Note: Try to create cache directory, but don't fail if filesystem has issues
4779
- try {
4780
- await tools.fs.mkdir(dirname(join(rootDirname, filepath)), { recursive: true });
4781
- }
4782
- catch (error) {
4783
- if (isVerbose) {
4784
- console.info(`📄 [3] "${name}" error creating cache directory`);
4785
- }
4786
- // Note: If we can't create cache directory, we'll handle it when trying to write the file
4787
- // This handles read-only filesystems, permission issues, and missing parent directories
4788
- if (error instanceof Error &&
4789
- (error.message.includes('EROFS') ||
4790
- error.message.includes('read-only') ||
4791
- error.message.includes('EACCES') ||
4792
- error.message.includes('EPERM') ||
4793
- error.message.includes('ENOENT'))) ;
4794
- else {
4795
- // Re-throw other unexpected errors
4796
- throw error;
4797
- }
4798
- }
4799
- const fileContent = Buffer.from(await response.arrayBuffer());
4800
- if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
4801
- throw new LimitReachedError(`File is too large (${Math.round(fileContent.length / 1024 / 1024)}MB). Maximum allowed size is ${Math.round(DEFAULT_MAX_FILE_SIZE / 1024 / 1024)}MB.`);
4802
- }
4803
- // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
4804
- try {
4805
- await tools.fs.writeFile(join(rootDirname, filepath), fileContent);
4806
- }
4807
- catch (error) {
4808
- if (isVerbose) {
4809
- console.info(`📄 [4] "${name}" error writing cache file`);
4810
- }
4811
- // Note: If we can't write to cache, we'll process the file directly from memory
4812
- // This handles read-only filesystems like Vercel
4813
- if (error instanceof Error &&
4814
- (error.message.includes('EROFS') ||
4815
- error.message.includes('read-only') ||
4816
- error.message.includes('EACCES') ||
4817
- error.message.includes('EPERM') ||
4818
- error.message.includes('ENOENT'))) {
4819
- // Return a handler that works directly with the downloaded content
4820
- return {
4821
- source: name,
4822
- filename: null,
4823
- url,
4824
- mimeType,
4825
- async asJson() {
4826
- return JSON.parse(fileContent.toString('utf-8'));
4827
- },
4828
- async asText() {
4829
- return fileContent.toString('utf-8');
4830
- },
4831
- };
4832
- }
4833
- else {
4834
- // Re-throw other unexpected errors
4835
- throw error;
4836
- }
4837
- }
4838
- // TODO: [💵] Check the file security
4839
- // TODO: [🧹][🧠] Delete the file after the scraping is done
4840
- if (isVerbose) {
4841
- console.info(`📄 [5] "${name}" cached at "${join(rootDirname, filepath)}"`);
4842
- }
4843
- return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
4844
- ...options,
4845
- rootDirname,
4846
- });
5413
+ const response = await fetch(url); // <- TODO: [🧠] Scraping and fetch proxy
5414
+ const mimeType = ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.split(';')[0]) || 'text/html';
5415
+ if (tools.fs === undefined || !url.endsWith('.pdf' /* <- TODO: [💵] */)) {
5416
+ return makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose);
5417
+ }
5418
+ return cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose);
5419
+ }
5420
+ /**
5421
+ * Creates a source handler that reads directly from a fetched response.
5422
+ *
5423
+ * @private internal utility of `makeKnowledgeSourceHandler`
5424
+ */
5425
+ function makeRemoteResponseKnowledgeSourceHandler(name, url, mimeType, response, isVerbose) {
5426
+ if (isVerbose) {
5427
+ console.info(`📄 [2] "${name}" tools.fs is not available or URL is not a PDF.`);
5428
+ }
5429
+ return {
5430
+ source: name,
5431
+ filename: null,
5432
+ url,
5433
+ mimeType,
5434
+ /*
5435
+ TODO: [🥽]
5436
+ > async asBlob() {
5437
+ > // TODO: [👨🏻‍🤝‍👨🏻] This can be called multiple times BUT when called second time, response in already consumed
5438
+ > const content = await response.blob();
5439
+ > return content;
5440
+ > },
5441
+ */
5442
+ async asJson() {
5443
+ // TODO: [👨🏻‍🤝‍👨🏻]
5444
+ const content = await response.json();
5445
+ return content;
5446
+ },
5447
+ async asText() {
5448
+ // TODO: [👨🏻‍🤝‍👨🏻]
5449
+ const content = await response.text();
5450
+ return content;
5451
+ },
5452
+ };
5453
+ }
5454
+ /**
5455
+ * Downloads a PDF knowledge source into cache when possible and falls back to in-memory content otherwise.
5456
+ *
5457
+ * @private internal utility of `makeKnowledgeSourceHandler`
5458
+ */
5459
+ async function cachePdfKnowledgeSourceHandler(url, name, mimeType, response, tools, options, isVerbose) {
5460
+ const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5461
+ const filepath = createDownloadedKnowledgeSourceFilepath(url, mimeType);
5462
+ const fullFilepath = join(rootDirname, filepath);
5463
+ await createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose);
5464
+ const fileContent = Buffer.from(await response.arrayBuffer());
5465
+ if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5466
+ 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.`);
5467
+ }
5468
+ const isCached = await writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose);
5469
+ if (!isCached) {
5470
+ return makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent);
5471
+ }
5472
+ // TODO: [💵] Check the file security
5473
+ // TODO: [🧹][🧠] Delete the file after the scraping is done
5474
+ if (isVerbose) {
5475
+ console.info(`📄 [5] "${name}" cached at "${fullFilepath}"`);
5476
+ }
5477
+ return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
5478
+ ...options,
5479
+ rootDirname,
5480
+ });
5481
+ }
5482
+ /**
5483
+ * Builds a stable cache filepath for a downloaded knowledge source.
5484
+ *
5485
+ * @private internal utility of `makeKnowledgeSourceHandler`
5486
+ */
5487
+ function createDownloadedKnowledgeSourceFilepath(url, mimeType) {
5488
+ const basename = url.split('/').pop() || titleToName(url);
5489
+ const hash = sha256(hexEncoder.parse(url)).toString( /* hex */);
5490
+ // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5491
+ return join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
5492
+ }
5493
+ /**
5494
+ * Tries to create the cache directory needed for a downloaded knowledge source.
5495
+ *
5496
+ * @private internal utility of `makeKnowledgeSourceHandler`
5497
+ */
5498
+ async function createCacheDirectoryIfPossible(name, fullFilepath, tools, isVerbose) {
5499
+ try {
5500
+ await tools.fs.mkdir(dirname(fullFilepath), { recursive: true });
4847
5501
  }
4848
- else if (isValidFilePath(knowledgeSourceContent)) {
4849
- if (tools.fs === undefined) {
4850
- throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
4851
- // <- TODO: [🧠] What is the best error type here`
5502
+ catch (error) {
5503
+ if (isVerbose) {
5504
+ console.info(`📄 [3] "${name}" error creating cache directory`);
4852
5505
  }
4853
- if (rootDirname === null) {
4854
- throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
4855
- // <- TODO: [🧠] What is the best error type here`
5506
+ // Note: If we can't create cache directory, we'll handle it when trying to write the file
5507
+ // This handles read-only filesystems, permission issues, and missing parent directories
5508
+ if (!isIgnorableCacheFilesystemError(error)) {
5509
+ throw error;
4856
5510
  }
4857
- const filename = isAbsolute(knowledgeSourceContent)
4858
- ? knowledgeSourceContent
4859
- : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5511
+ }
5512
+ }
5513
+ /**
5514
+ * Tries to write downloaded content into cache and reports whether the cache was usable.
5515
+ *
5516
+ * @private internal utility of `makeKnowledgeSourceHandler`
5517
+ */
5518
+ async function writeCacheFileIfPossible(name, fullFilepath, fileContent, tools, isVerbose) {
5519
+ // Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
5520
+ try {
5521
+ await tools.fs.writeFile(fullFilepath, fileContent);
5522
+ return true;
5523
+ }
5524
+ catch (error) {
4860
5525
  if (isVerbose) {
4861
- console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5526
+ console.info(`📄 [4] "${name}" error writing cache file`);
4862
5527
  }
4863
- const fileExtension = getFileExtension(filename);
4864
- const mimeType = extensionToMimeType(fileExtension || '');
4865
- if (!(await isFileExisting(filename, tools.fs))) {
4866
- throw new NotFoundError(spaceTrim$1((block) => `
4867
- Can not make source handler for file which does not exist:
5528
+ // Note: If we can't write to cache, we'll process the file directly from memory
5529
+ // This handles read-only filesystems like Vercel
5530
+ if (isIgnorableCacheFilesystemError(error)) {
5531
+ return false;
5532
+ }
5533
+ throw error;
5534
+ }
5535
+ }
5536
+ /**
5537
+ * Detects filesystem errors that should not fail optional caching.
5538
+ *
5539
+ * @private internal utility of `makeKnowledgeSourceHandler`
5540
+ */
5541
+ function isIgnorableCacheFilesystemError(error) {
5542
+ return (error instanceof Error &&
5543
+ (error.message.includes('EROFS') ||
5544
+ error.message.includes('read-only') ||
5545
+ error.message.includes('EACCES') ||
5546
+ error.message.includes('EPERM') ||
5547
+ error.message.includes('ENOENT')));
5548
+ }
5549
+ /**
5550
+ * Creates a source handler backed by already downloaded file content kept in memory.
5551
+ *
5552
+ * @private internal utility of `makeKnowledgeSourceHandler`
5553
+ */
5554
+ function makeBufferedKnowledgeSourceHandler(name, url, mimeType, fileContent) {
5555
+ return {
5556
+ source: name,
5557
+ filename: null,
5558
+ url,
5559
+ mimeType,
5560
+ async asJson() {
5561
+ return JSON.parse(fileContent.toString('utf-8'));
5562
+ },
5563
+ async asText() {
5564
+ return fileContent.toString('utf-8');
5565
+ },
5566
+ };
5567
+ }
5568
+ /**
5569
+ * Creates a source handler for file-based knowledge.
5570
+ *
5571
+ * @private internal utility of `makeKnowledgeSourceHandler`
5572
+ */
5573
+ async function makeFileKnowledgeSourceHandler(knowledgeSourceContent, name, tools, rootDirname, isVerbose) {
5574
+ if (tools.fs === undefined) {
5575
+ throw new EnvironmentMismatchError('Can not import file knowledge without filesystem tools');
5576
+ // <- TODO: [🧠] What is the best error type here`
5577
+ }
5578
+ if (rootDirname === null) {
5579
+ throw new EnvironmentMismatchError('Can not import file knowledge in non-file pipeline');
5580
+ // <- TODO: [🧠] What is the best error type here`
5581
+ }
5582
+ const filename = isAbsolute(knowledgeSourceContent)
5583
+ ? knowledgeSourceContent
5584
+ : join(rootDirname, knowledgeSourceContent).split('\\').join('/');
5585
+ if (isVerbose) {
5586
+ console.info(`📄 [6] "${name}" is a valid file "${filename}"`);
5587
+ }
5588
+ const fileExtension = getFileExtension(filename);
5589
+ const mimeType = extensionToMimeType(fileExtension || '');
5590
+ if (!(await isFileExisting(filename, tools.fs))) {
5591
+ throw new NotFoundError(spaceTrim$1((block) => `
5592
+ Can not make source handler for file which does not exist:
4868
5593
 
4869
- File:
4870
- ${block(knowledgeSourceContent)}
5594
+ File:
5595
+ ${block(knowledgeSourceContent)}
4871
5596
 
4872
- Full file path:
4873
- ${block(filename)}
4874
- `));
4875
- }
4876
- // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
4877
- return {
4878
- source: name,
4879
- filename,
4880
- url: null,
4881
- mimeType,
4882
- /*
4883
- TODO: [🥽]
4884
- > async asBlob() {
4885
- > const content = await tools.fs!.readFile(filename);
4886
- > return new Blob(
4887
- > [
4888
- > content,
4889
- > // <- TODO: [🥽] This is NOT tested, test it
4890
- > ],
4891
- > { type: mimeType },
4892
- > );
4893
- > },
4894
- */
4895
- async asJson() {
4896
- return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
4897
- },
4898
- async asText() {
4899
- return await tools.fs.readFile(filename, 'utf-8');
4900
- },
4901
- };
5597
+ Full file path:
5598
+ ${block(filename)}
5599
+ `));
4902
5600
  }
4903
- else {
4904
- if (isVerbose) {
4905
- console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
4906
- console.info('---');
4907
- console.info(knowledgeSourceContent);
4908
- console.info('---');
4909
- }
4910
- return {
4911
- source: name,
4912
- filename: null,
4913
- url: null,
4914
- mimeType: 'text/markdown',
4915
- asText() {
4916
- return knowledgeSource.knowledgeSourceContent;
4917
- },
4918
- asJson() {
4919
- throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
4920
- },
4921
- /*
4922
- TODO: [🥽]
4923
- > asBlob() {
4924
- > throw new UnexpectedError(
4925
- > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
4926
- > );
4927
- > },
4928
- */
4929
- };
5601
+ // TODO: [🧠][😿] Test security file - file is scoped to the project (BUT maybe do this in `filesystemTools`)
5602
+ return {
5603
+ source: name,
5604
+ filename,
5605
+ url: null,
5606
+ mimeType,
5607
+ /*
5608
+ TODO: [🥽]
5609
+ > async asBlob() {
5610
+ > const content = await tools.fs!.readFile(filename);
5611
+ > return new Blob(
5612
+ > [
5613
+ > content,
5614
+ > // <- TODO: [🥽] This is NOT tested, test it
5615
+ > ],
5616
+ > { type: mimeType },
5617
+ > );
5618
+ > },
5619
+ */
5620
+ async asJson() {
5621
+ return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
5622
+ },
5623
+ async asText() {
5624
+ return await tools.fs.readFile(filename, 'utf-8');
5625
+ },
5626
+ };
5627
+ }
5628
+ /**
5629
+ * Creates a source handler for inline text knowledge.
5630
+ *
5631
+ * @private internal utility of `makeKnowledgeSourceHandler`
5632
+ */
5633
+ function makeInlineTextKnowledgeSourceHandler(knowledgeSourceContent, name, isVerbose) {
5634
+ if (isVerbose) {
5635
+ console.info(`📄 [7] "${name}" is just a explicit string text with a knowledge source`);
5636
+ console.info('---');
5637
+ console.info(knowledgeSourceContent);
5638
+ console.info('---');
4930
5639
  }
5640
+ return {
5641
+ source: name,
5642
+ filename: null,
5643
+ url: null,
5644
+ mimeType: 'text/markdown',
5645
+ asText() {
5646
+ return knowledgeSourceContent;
5647
+ },
5648
+ asJson() {
5649
+ throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
5650
+ },
5651
+ /*
5652
+ TODO: [🥽]
5653
+ > asBlob() {
5654
+ > throw new UnexpectedError(
5655
+ > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
5656
+ > );
5657
+ > },
5658
+ */
5659
+ };
4931
5660
  }
4932
5661
 
4933
5662
  /**
@@ -6020,204 +6749,102 @@ const CountUtils = {
6020
6749
  // TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
6021
6750
  // Note: [💞] Ignore a discrepancy between file name and entity name
6022
6751
 
6023
- /**
6024
- * Function checkExpectations will check if the expectations on given value are met
6025
- *
6026
- * Note: There are two similar functions:
6027
- * - `checkExpectations` which throws an error if the expectations are not met
6028
- * - `isPassingExpectations` which returns a boolean
6029
- *
6030
- * @throws {ExpectError} if the expectations are not met
6031
- * @returns {void} Nothing
6032
- *
6033
- * @private internal function of `createPipelineExecutor`
6034
- */
6035
- function checkExpectations(expectations, value) {
6036
- for (const [unit, { max, min }] of Object.entries(expectations)) {
6037
- const amount = CountUtils[unit.toUpperCase()](value);
6038
- if (min && amount < min) {
6039
- throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6040
- } /* not else */
6041
- if (max && amount > max) {
6042
- throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6043
- }
6044
- }
6045
- }
6046
- // TODO: [💝] Unite object for expecting amount and format
6047
- // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6048
- // Note: [💝] and [🤠] are interconnected together
6049
-
6050
- /**
6051
- * Validates a prompt result against expectations and format requirements.
6052
- * This function provides a common abstraction for result validation that can be used
6053
- * by both execution logic and caching logic to ensure consistency.
6054
- *
6055
- * Note: [🔂] This function is idempotent.
6056
- *
6057
- * @param options - The validation options including result string, expectations, and format
6058
- * @returns Validation result with processed string and validity status
6059
- *
6060
- * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6061
- */
6062
- function validatePromptResult(options) {
6063
- const { resultString, expectations, format } = options;
6064
- let processedResultString = resultString;
6065
- let validationError;
6066
- try {
6067
- // TODO: [💝] Unite object for expecting amount and format
6068
- if (format) {
6069
- if (format === 'JSON') {
6070
- if (!isValidJsonString(processedResultString)) {
6071
- // TODO: [🏢] Do more universally via `FormatParser`
6072
- try {
6073
- processedResultString = extractJsonBlock(processedResultString);
6074
- }
6075
- catch (error) {
6076
- keepUnused(error);
6077
- throw new ExpectError(spaceTrim$1((block) => `
6078
- Expected valid JSON string
6079
-
6080
- The expected JSON text:
6081
- ${block(processedResultString)}
6082
- `));
6083
- }
6084
- }
6085
- }
6086
- else {
6087
- throw new UnexpectedError(`Unknown format "${format}"`);
6088
- }
6089
- }
6090
- // TODO: [💝] Unite object for expecting amount and format
6091
- if (expectations) {
6092
- checkExpectations(expectations, processedResultString);
6093
- }
6094
- return {
6095
- isValid: true,
6096
- processedResultString,
6097
- };
6098
- }
6099
- catch (error) {
6100
- if (error instanceof ExpectError) {
6101
- validationError = error;
6102
- }
6103
- else {
6104
- // Re-throw non-ExpectError errors (like UnexpectedError)
6105
- throw error;
6106
- }
6107
- return {
6108
- isValid: false,
6109
- processedResultString,
6110
- error: validationError,
6111
- };
6112
- }
6113
- }
6114
-
6115
- /**
6116
- * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
6117
- * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
6118
- * Throws errors if execution fails after all attempts.
6119
- *
6120
- * @param options - The options for execution, including task, parameters, pipeline, and configuration.
6121
- * @returns The result string of the executed task.
6122
- *
6123
- * @private internal utility of `createPipelineExecutor`
6124
- */
6125
- async function executeAttempts(options) {
6126
- const $ongoingTaskResult = createOngoingTaskResult();
6127
- const llmTools = getSingleLlmExecutionTools(options.tools.llm);
6128
- attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
6129
- const attempt = createAttemptDescriptor({
6130
- attemptIndex,
6131
- jokerParameterNames: options.jokerParameterNames,
6132
- pipelineIdentification: options.pipelineIdentification,
6133
- });
6134
- resetAttemptExecutionState($ongoingTaskResult);
6135
- try {
6136
- await executeSingleAttempt({
6137
- attempt,
6138
- options,
6139
- llmTools,
6140
- $ongoingTaskResult,
6141
- });
6142
- break attempts;
6143
- }
6144
- catch (error) {
6145
- if (!(error instanceof ExpectError)) {
6146
- throw error;
6147
- }
6148
- recordFailedAttempt({
6149
- error,
6150
- attemptIndex,
6151
- onProgress: options.onProgress,
6152
- $ongoingTaskResult,
6153
- });
6154
- }
6155
- finally {
6156
- reportPromptExecution({
6157
- attempt,
6158
- task: options.task,
6159
- $executionReport: options.$executionReport,
6160
- logLlmCall: options.logLlmCall,
6161
- $ongoingTaskResult,
6162
- });
6163
- }
6164
- throwIfFinalAttemptFailed({
6165
- attemptIndex,
6166
- maxAttempts: options.maxAttempts,
6167
- maxExecutionAttempts: options.maxExecutionAttempts,
6168
- pipelineIdentification: options.pipelineIdentification,
6169
- $ongoingTaskResult,
6170
- });
6171
- }
6172
- return getSuccessfulResultString({
6173
- pipelineIdentification: options.pipelineIdentification,
6174
- $ongoingTaskResult,
6175
- });
6176
- }
6177
- /**
6178
- * Creates mutable attempt state for one task execution lifecycle.
6179
- */
6180
- function createOngoingTaskResult() {
6181
- return {
6182
- $result: null,
6183
- $resultString: null,
6184
- $expectError: null,
6185
- $scriptPipelineExecutionErrors: [],
6186
- $failedResults: [],
6187
- };
6188
- }
6189
- /**
6190
- * Resolves the bookkeeping for one loop iteration, including joker lookup.
6191
- */
6192
- function createAttemptDescriptor(options) {
6193
- const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
6194
- const isJokerAttempt = attemptIndex < 0;
6195
- const jokerParameterName = isJokerAttempt
6196
- ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
6197
- : undefined;
6198
- if (isJokerAttempt && !jokerParameterName) {
6199
- throw new UnexpectedError(spaceTrim$1((block) => `
6200
- Joker not found in attempt ${attemptIndex}
6201
-
6202
- ${block(pipelineIdentification)}
6203
- `));
6752
+ /**
6753
+ * Function checkExpectations will check if the expectations on given value are met
6754
+ *
6755
+ * Note: There are two similar functions:
6756
+ * - `checkExpectations` which throws an error if the expectations are not met
6757
+ * - `isPassingExpectations` which returns a boolean
6758
+ *
6759
+ * @throws {ExpectError} if the expectations are not met
6760
+ * @returns {void} Nothing
6761
+ *
6762
+ * @private internal function of `createPipelineExecutor`
6763
+ */
6764
+ function checkExpectations(expectations, value) {
6765
+ for (const [unit, { max, min }] of Object.entries(expectations)) {
6766
+ const amount = CountUtils[unit.toUpperCase()](value);
6767
+ if (min && amount < min) {
6768
+ throw new ExpectError(`Expected at least ${min} ${unit} but got ${amount}`);
6769
+ } /* not else */
6770
+ if (max && amount > max) {
6771
+ throw new ExpectError(`Expected at most ${max} ${unit} but got ${amount}`);
6772
+ }
6204
6773
  }
6205
- return {
6206
- attemptIndex,
6207
- isJokerAttempt,
6208
- jokerParameterName,
6209
- };
6210
6774
  }
6775
+ // TODO: [💝] Unite object for expecting amount and format
6776
+ // TODO: [🧠][🤠] This should be part of `TextFormatParser`
6777
+ // Note: [💝] and [🤠] are interconnected together
6778
+
6211
6779
  /**
6212
- * Clears the per-attempt result slots while preserving cumulative failure history.
6780
+ * Validates a prompt result against expectations and format requirements.
6781
+ * This function provides a common abstraction for result validation that can be used
6782
+ * by both execution logic and caching logic to ensure consistency.
6783
+ *
6784
+ * Note: [🔂] This function is idempotent.
6785
+ *
6786
+ * @param options - The validation options including result string, expectations, and format
6787
+ * @returns Validation result with processed string and validity status
6788
+ *
6789
+ * @private internal function of `createPipelineExecutor` and `cacheLlmTools`
6213
6790
  */
6214
- function resetAttemptExecutionState($ongoingTaskResult) {
6215
- $ongoingTaskResult.$result = null;
6216
- $ongoingTaskResult.$resultString = null;
6217
- $ongoingTaskResult.$expectError = null;
6791
+ function validatePromptResult(options) {
6792
+ const { resultString, expectations, format } = options;
6793
+ let processedResultString = resultString;
6794
+ let validationError;
6795
+ try {
6796
+ // TODO: [💝] Unite object for expecting amount and format
6797
+ if (format) {
6798
+ if (format === 'JSON') {
6799
+ if (!isValidJsonString(processedResultString)) {
6800
+ // TODO: [🏢] Do more universally via `FormatParser`
6801
+ try {
6802
+ processedResultString = extractJsonBlock(processedResultString);
6803
+ }
6804
+ catch (error) {
6805
+ keepUnused(error);
6806
+ throw new ExpectError(spaceTrim$1((block) => `
6807
+ Expected valid JSON string
6808
+
6809
+ The expected JSON text:
6810
+ ${block(processedResultString)}
6811
+ `));
6812
+ }
6813
+ }
6814
+ }
6815
+ else {
6816
+ throw new UnexpectedError(`Unknown format "${format}"`);
6817
+ }
6818
+ }
6819
+ // TODO: [💝] Unite object for expecting amount and format
6820
+ if (expectations) {
6821
+ checkExpectations(expectations, processedResultString);
6822
+ }
6823
+ return {
6824
+ isValid: true,
6825
+ processedResultString,
6826
+ };
6827
+ }
6828
+ catch (error) {
6829
+ if (error instanceof ExpectError) {
6830
+ validationError = error;
6831
+ }
6832
+ else {
6833
+ // Re-throw non-ExpectError errors (like UnexpectedError)
6834
+ throw error;
6835
+ }
6836
+ return {
6837
+ isValid: false,
6838
+ processedResultString,
6839
+ error: validationError,
6840
+ };
6841
+ }
6218
6842
  }
6843
+
6219
6844
  /**
6220
6845
  * Executes one loop iteration, from joker resolution or task execution through validation.
6846
+ *
6847
+ * @private function of `executeAttempts`
6221
6848
  */
6222
6849
  async function executeSingleAttempt(options) {
6223
6850
  const { attempt, options: executeAttemptsOptions, llmTools, $ongoingTaskResult } = options;
@@ -6532,11 +7159,15 @@ function validateAttemptResult(options) {
6532
7159
  // Update the result string in case format processing modified it (e.g., JSON extraction)
6533
7160
  $ongoingTaskResult.$resultString = validationResult.processedResultString;
6534
7161
  }
7162
+
6535
7163
  /**
6536
- * Stores one failed attempt and reports the expectation error upstream.
7164
+ * Stores one failed attempt, reports the expectation error, and throws the aggregated retry error after the final
7165
+ * regular attempt.
7166
+ *
7167
+ * @private function of `executeAttempts`
6537
7168
  */
6538
- function recordFailedAttempt(options) {
6539
- const { error, attemptIndex, onProgress, $ongoingTaskResult } = options;
7169
+ function handleAttemptFailure(options) {
7170
+ const { error, attemptIndex, maxAttempts, maxExecutionAttempts, onProgress, pipelineIdentification, $ongoingTaskResult, } = options;
6540
7171
  $ongoingTaskResult.$expectError = error;
6541
7172
  $ongoingTaskResult.$failedResults.push({
6542
7173
  attemptIndex,
@@ -6546,39 +7177,7 @@ function recordFailedAttempt(options) {
6546
7177
  onProgress({
6547
7178
  errors: [error],
6548
7179
  });
6549
- }
6550
- /**
6551
- * Appends the prompt execution report for prompt-task attempts.
6552
- */
6553
- function reportPromptExecution(options) {
6554
- const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
6555
- if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
6556
- return;
6557
- }
6558
- // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
6559
- // In that case we don’t want to make a report about it because it’s not a llm execution error
6560
- const executionPromptReport = {
6561
- prompt: {
6562
- ...$ongoingTaskResult.$prompt,
6563
- // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
6564
- },
6565
- result: $ongoingTaskResult.$result || undefined,
6566
- error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
6567
- };
6568
- $executionReport.promptExecutions.push(executionPromptReport);
6569
- if (logLlmCall) {
6570
- logLlmCall({
6571
- modelName: 'model' /* <- TODO: How to get model name from the report */,
6572
- report: executionPromptReport,
6573
- });
6574
- }
6575
- }
6576
- /**
6577
- * Throws the aggregated retry error after the last regular attempt fails expectations.
6578
- */
6579
- function throwIfFinalAttemptFailed(options) {
6580
- const { attemptIndex, maxAttempts, maxExecutionAttempts, pipelineIdentification, $ongoingTaskResult } = options;
6581
- if ($ongoingTaskResult.$expectError === null || attemptIndex !== maxAttempts - 1) {
7180
+ if (attemptIndex !== maxAttempts - 1) {
6582
7181
  return;
6583
7182
  }
6584
7183
  throw new PipelineExecutionError(spaceTrim$1((block) => {
@@ -6623,6 +7222,136 @@ function quoteMultilineText(text) {
6623
7222
  .map((line) => `> ${line}`)
6624
7223
  .join('\n');
6625
7224
  }
7225
+
7226
+ /**
7227
+ * Appends the prompt execution report for prompt-task attempts.
7228
+ *
7229
+ * @private function of `executeAttempts`
7230
+ */
7231
+ function reportPromptExecution(options) {
7232
+ const { attempt, task, $executionReport, logLlmCall, $ongoingTaskResult } = options;
7233
+ if (attempt.isJokerAttempt || task.taskType !== 'PROMPT_TASK' || !$ongoingTaskResult.$prompt) {
7234
+ return;
7235
+ }
7236
+ // Note: [2] When some expected parameter is not defined, error will occur in templateParameters
7237
+ // In that case we don’t want to make a report about it because it’s not a llm execution error
7238
+ const executionPromptReport = {
7239
+ prompt: {
7240
+ ...$ongoingTaskResult.$prompt,
7241
+ // <- TODO: [🧠] How to pick everyhing except `pipelineUrl`
7242
+ },
7243
+ result: $ongoingTaskResult.$result || undefined,
7244
+ error: $ongoingTaskResult.$expectError === null ? undefined : serializeError($ongoingTaskResult.$expectError),
7245
+ };
7246
+ $executionReport.promptExecutions.push(executionPromptReport);
7247
+ if (logLlmCall) {
7248
+ logLlmCall({
7249
+ modelName: 'model' /* <- TODO: How to get model name from the report */,
7250
+ report: executionPromptReport,
7251
+ });
7252
+ }
7253
+ }
7254
+
7255
+ /**
7256
+ * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
7257
+ * (prompt, script, dialog, etc.), applies postprocessing, checks expectations, and updates the execution report.
7258
+ * Throws errors if execution fails after all attempts.
7259
+ *
7260
+ * @param options - The options for execution, including task, parameters, pipeline, and configuration.
7261
+ * @returns The result string of the executed task.
7262
+ *
7263
+ * @private internal utility of `createPipelineExecutor`
7264
+ */
7265
+ async function executeAttempts(options) {
7266
+ const $ongoingTaskResult = createOngoingTaskResult();
7267
+ const llmTools = getSingleLlmExecutionTools(options.tools.llm);
7268
+ attempts: for (let attemptIndex = -options.jokerParameterNames.length; attemptIndex < options.maxAttempts; attemptIndex++) {
7269
+ const attempt = createAttemptDescriptor({
7270
+ attemptIndex,
7271
+ jokerParameterNames: options.jokerParameterNames,
7272
+ pipelineIdentification: options.pipelineIdentification,
7273
+ });
7274
+ resetAttemptExecutionState($ongoingTaskResult);
7275
+ try {
7276
+ await executeSingleAttempt({
7277
+ attempt,
7278
+ options,
7279
+ llmTools,
7280
+ $ongoingTaskResult,
7281
+ });
7282
+ break attempts;
7283
+ }
7284
+ catch (error) {
7285
+ if (!(error instanceof ExpectError)) {
7286
+ throw error;
7287
+ }
7288
+ handleAttemptFailure({
7289
+ error,
7290
+ attemptIndex,
7291
+ maxAttempts: options.maxAttempts,
7292
+ maxExecutionAttempts: options.maxExecutionAttempts,
7293
+ onProgress: options.onProgress,
7294
+ pipelineIdentification: options.pipelineIdentification,
7295
+ $ongoingTaskResult,
7296
+ });
7297
+ }
7298
+ finally {
7299
+ reportPromptExecution({
7300
+ attempt,
7301
+ task: options.task,
7302
+ $executionReport: options.$executionReport,
7303
+ logLlmCall: options.logLlmCall,
7304
+ $ongoingTaskResult,
7305
+ });
7306
+ }
7307
+ }
7308
+ return getSuccessfulResultString({
7309
+ pipelineIdentification: options.pipelineIdentification,
7310
+ $ongoingTaskResult,
7311
+ });
7312
+ }
7313
+ /**
7314
+ * Creates mutable attempt state for one task execution lifecycle.
7315
+ */
7316
+ function createOngoingTaskResult() {
7317
+ return {
7318
+ $result: null,
7319
+ $resultString: null,
7320
+ $expectError: null,
7321
+ $scriptPipelineExecutionErrors: [],
7322
+ $failedResults: [],
7323
+ };
7324
+ }
7325
+ /**
7326
+ * Resolves the bookkeeping for one loop iteration, including joker lookup.
7327
+ */
7328
+ function createAttemptDescriptor(options) {
7329
+ const { attemptIndex, jokerParameterNames, pipelineIdentification } = options;
7330
+ const isJokerAttempt = attemptIndex < 0;
7331
+ const jokerParameterName = isJokerAttempt
7332
+ ? jokerParameterNames[jokerParameterNames.length + attemptIndex]
7333
+ : undefined;
7334
+ if (isJokerAttempt && !jokerParameterName) {
7335
+ throw new UnexpectedError(spaceTrim$1((block) => `
7336
+ Joker not found in attempt ${attemptIndex}
7337
+
7338
+ ${block(pipelineIdentification)}
7339
+ `));
7340
+ }
7341
+ return {
7342
+ attemptIndex,
7343
+ isJokerAttempt,
7344
+ jokerParameterName,
7345
+ };
7346
+ }
7347
+ /**
7348
+ * Clears the per-attempt result slots while preserving cumulative failure history.
7349
+ */
7350
+ function resetAttemptExecutionState($ongoingTaskResult) {
7351
+ $ongoingTaskResult.$result = null;
7352
+ $ongoingTaskResult.$resultString = null;
7353
+ $ongoingTaskResult.$expectError = null;
7354
+ }
6626
7355
  /**
6627
7356
  * Returns the successful result string or raises an unexpected internal-state error.
6628
7357
  */