@promptbook/markitdown 0.112.0-72 → 0.112.0-79

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