@promptbook/documents 0.112.0-72 → 0.112.0-79

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