@promptbook/cli 0.104.0-1 → 0.104.0-11

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 (229) hide show
  1. package/apps/agents-server/config.ts +1 -3
  2. package/apps/agents-server/next.config.ts +2 -2
  3. package/apps/agents-server/package.json +7 -3
  4. package/apps/agents-server/public/fonts/OpenMoji-color-cbdt.woff2 +0 -0
  5. package/apps/agents-server/public/swagger.json +115 -0
  6. package/apps/agents-server/scripts/generate-reserved-paths/generate-reserved-paths.ts +54 -0
  7. package/apps/agents-server/scripts/generate-reserved-paths/tsconfig.json +19 -0
  8. package/apps/agents-server/src/app/AddAgentButton.tsx +47 -21
  9. package/apps/agents-server/src/app/actions.ts +22 -5
  10. package/apps/agents-server/src/app/admin/browser-test/BrowserTestClient.tsx +211 -0
  11. package/apps/agents-server/src/app/admin/browser-test/page.tsx +13 -0
  12. package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +221 -274
  13. package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +94 -137
  14. package/apps/agents-server/src/app/admin/files/FilesGalleryClient.tsx +263 -0
  15. package/apps/agents-server/src/app/admin/files/actions.ts +61 -0
  16. package/apps/agents-server/src/app/admin/files/page.tsx +13 -0
  17. package/apps/agents-server/src/app/admin/image-generator-test/ImageGeneratorTestClient.tsx +169 -0
  18. package/apps/agents-server/src/app/admin/image-generator-test/page.tsx +13 -0
  19. package/apps/agents-server/src/app/admin/images/ImagesGalleryClient.tsx +256 -0
  20. package/apps/agents-server/src/app/admin/images/actions.ts +60 -0
  21. package/apps/agents-server/src/app/admin/images/page.tsx +13 -0
  22. package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +294 -0
  23. package/apps/agents-server/src/app/admin/messages/page.tsx +13 -0
  24. package/apps/agents-server/src/app/admin/messages/send-email/SendEmailClient.tsx +104 -0
  25. package/apps/agents-server/src/app/admin/messages/send-email/actions.ts +35 -0
  26. package/apps/agents-server/src/app/admin/messages/send-email/page.tsx +13 -0
  27. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +23 -19
  28. package/apps/agents-server/src/app/admin/search-engine-test/SearchEngineTestClient.tsx +109 -0
  29. package/apps/agents-server/src/app/admin/search-engine-test/actions.ts +17 -0
  30. package/apps/agents-server/src/app/admin/search-engine-test/page.tsx +13 -0
  31. package/apps/agents-server/src/app/agents/[agentName]/AgentChatWrapper.tsx +15 -1
  32. package/apps/agents-server/src/app/agents/[agentName]/AgentOptionsMenu.tsx +51 -9
  33. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +47 -4
  34. package/apps/agents-server/src/app/agents/[agentName]/AgentProfileWrapper.tsx +53 -11
  35. package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +23 -3
  36. package/apps/agents-server/src/app/agents/[agentName]/agentLinks.tsx +8 -8
  37. package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +17 -26
  38. package/apps/agents-server/src/app/agents/[agentName]/api/book/route.ts +4 -2
  39. package/apps/agents-server/src/app/agents/[agentName]/api/chat/route.ts +20 -0
  40. package/apps/agents-server/src/app/agents/[agentName]/api/mcp/route.ts +6 -11
  41. package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +5 -1
  42. package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +5 -2
  43. package/apps/agents-server/src/app/agents/[agentName]/book/BookEditorWrapper.tsx +20 -16
  44. package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +15 -2
  45. package/apps/agents-server/src/app/agents/[agentName]/book+chat/page.tsx +15 -2
  46. package/apps/agents-server/src/app/agents/[agentName]/chat/page.tsx +12 -0
  47. package/apps/agents-server/src/app/agents/[agentName]/code/api/route.ts +68 -0
  48. package/apps/agents-server/src/app/agents/[agentName]/code/page.tsx +223 -0
  49. package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +5 -0
  50. package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +2 -2
  51. package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +10 -3
  52. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/getAgentDefaultAvatarPrompt.ts +31 -0
  53. package/apps/agents-server/src/app/agents/[agentName]/images/default-avatar.png/route.ts +194 -0
  54. package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +14 -2
  55. package/apps/agents-server/src/app/agents/[agentName]/images/page.tsx +200 -0
  56. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +4 -3
  57. package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +4 -3
  58. package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +10 -3
  59. package/apps/agents-server/src/app/agents/[agentName]/links/page.tsx +11 -4
  60. package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +11 -2
  61. package/apps/agents-server/src/app/agents/[agentName]/page.tsx +18 -10
  62. package/apps/agents-server/src/app/agents/[agentName]/system-message/page.tsx +100 -0
  63. package/apps/agents-server/src/app/api/admin-email/route.ts +12 -0
  64. package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +13 -14
  65. package/apps/agents-server/src/app/api/agents/[agentName]/restore/route.ts +20 -0
  66. package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +43 -1
  67. package/apps/agents-server/src/app/api/agents/route.ts +28 -3
  68. package/apps/agents-server/src/app/api/api-tokens/route.ts +6 -7
  69. package/apps/agents-server/src/app/api/browser-test/act/route.ts +141 -0
  70. package/apps/agents-server/src/app/api/browser-test/screenshot/route.ts +30 -0
  71. package/apps/agents-server/src/app/api/browser-test/scroll-facebook/route.ts +62 -0
  72. package/apps/agents-server/src/app/api/docs/book.md/route.ts +61 -0
  73. package/apps/agents-server/src/app/api/emails/incoming/sendgrid/route.ts +48 -0
  74. package/apps/agents-server/src/app/api/federated-agents/route.ts +12 -0
  75. package/apps/agents-server/src/app/api/images/[filename]/route.ts +128 -0
  76. package/apps/agents-server/src/app/api/messages/route.ts +102 -0
  77. package/apps/agents-server/src/app/api/metadata/route.ts +5 -6
  78. package/apps/agents-server/src/app/api/upload/route.ts +128 -45
  79. package/apps/agents-server/src/app/docs/[docId]/page.tsx +2 -3
  80. package/apps/agents-server/src/app/docs/page.tsx +12 -12
  81. package/apps/agents-server/src/app/globals.css +140 -33
  82. package/apps/agents-server/src/app/humans.txt/route.ts +1 -1
  83. package/apps/agents-server/src/app/layout.tsx +27 -22
  84. package/apps/agents-server/src/app/page.tsx +54 -6
  85. package/apps/agents-server/src/app/recycle-bin/actions.ts +20 -14
  86. package/apps/agents-server/src/app/recycle-bin/page.tsx +27 -41
  87. package/apps/agents-server/src/app/robots.txt/route.ts +1 -1
  88. package/apps/agents-server/src/app/security.txt/route.ts +1 -1
  89. package/apps/agents-server/src/app/sitemap.xml/route.ts +9 -7
  90. package/apps/agents-server/src/app/swagger/page.tsx +14 -0
  91. package/apps/agents-server/src/components/AgentProfile/AgentCapabilityChips.tsx +38 -0
  92. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +44 -116
  93. package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +92 -0
  94. package/apps/agents-server/src/components/AgentProfile/QrCodeModal.tsx +0 -1
  95. package/apps/agents-server/src/components/AgentProfile/useAgentBackground.ts +97 -0
  96. package/apps/agents-server/src/components/Auth/AuthControls.tsx +5 -4
  97. package/apps/agents-server/src/components/DeletedAgentBanner.tsx +26 -0
  98. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +38 -0
  99. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +11 -9
  100. package/apps/agents-server/src/components/Footer/Footer.tsx +5 -5
  101. package/apps/agents-server/src/components/ForgottenPasswordDialog/ForgottenPasswordDialog.tsx +61 -0
  102. package/apps/agents-server/src/components/Header/Header.tsx +130 -40
  103. package/apps/agents-server/src/components/Homepage/AgentCard.tsx +150 -23
  104. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +93 -15
  105. package/apps/agents-server/src/components/Homepage/DeletedAgentsList.tsx +66 -0
  106. package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +12 -3
  107. package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +19 -10
  108. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +3 -2
  109. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +50 -1
  110. package/apps/agents-server/src/components/NewAgentDialog/NewAgentDialog.tsx +88 -0
  111. package/apps/agents-server/src/components/NotFoundPage/NotFoundPage.tsx +7 -2
  112. package/apps/agents-server/src/components/OpenMojiIcon/OpenMojiIcon.tsx +16 -7
  113. package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -4
  114. package/apps/agents-server/src/components/RegisterUserDialog/RegisterUserDialog.tsx +61 -0
  115. package/apps/agents-server/src/components/VercelDeploymentCard/VercelDeploymentCard.tsx +2 -0
  116. package/apps/agents-server/src/components/_utils/generateMetaTxt.ts +12 -10
  117. package/apps/agents-server/src/components/_utils/headlessParam.tsx +7 -3
  118. package/apps/agents-server/src/database/$getTableName.ts +1 -0
  119. package/apps/agents-server/src/database/$provideSupabaseForBrowser.ts +3 -3
  120. package/apps/agents-server/src/database/$provideSupabaseForServer.ts +1 -1
  121. package/apps/agents-server/src/database/$provideSupabaseForWorker.ts +3 -3
  122. package/apps/agents-server/src/database/metadataDefaults.ts +19 -1
  123. package/apps/agents-server/src/database/migrate.ts +34 -1
  124. package/apps/agents-server/src/database/migrations/2025-11-0001-initial-schema.sql +1 -3
  125. package/apps/agents-server/src/database/migrations/2025-11-0002-metadata-table.sql +1 -3
  126. package/apps/agents-server/src/database/migrations/2025-12-0240-agent-public-id.sql +3 -0
  127. package/apps/agents-server/src/database/migrations/2025-12-0360-agent-deleted-at.sql +1 -0
  128. package/apps/agents-server/src/database/migrations/2025-12-0370-image-table.sql +19 -0
  129. package/apps/agents-server/src/database/migrations/2025-12-0380-agent-visibility.sql +1 -0
  130. package/apps/agents-server/src/database/migrations/2025-12-0390-upload-tracking.sql +20 -0
  131. package/apps/agents-server/src/database/migrations/2025-12-0401-file-upload-status.sql +13 -0
  132. package/apps/agents-server/src/database/migrations/2025-12-0402-message-table.sql +42 -0
  133. package/apps/agents-server/src/database/migrations/2025-12-0403-generation-lock-table.sql +15 -0
  134. package/apps/agents-server/src/database/migrations/2025-12-0640-openai-assistant-cache.sql +12 -0
  135. package/apps/agents-server/src/database/migrations/2025-12-0820-agent-history-permanent-id.sql +29 -0
  136. package/apps/agents-server/src/database/migrations/2025-12-0830-image-purpose.sql +5 -0
  137. package/apps/agents-server/src/database/migrations/2025-12-0890-file-agent-id.sql +5 -0
  138. package/apps/agents-server/src/database/schema.ts +244 -4
  139. package/apps/agents-server/src/generated/reservedPaths.ts +32 -0
  140. package/apps/agents-server/src/message-providers/email/_common/Email.ts +73 -0
  141. package/apps/agents-server/src/message-providers/email/_common/utils/TODO.txt +1 -0
  142. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.test.ts.todo +108 -0
  143. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddress.ts +62 -0
  144. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.test.ts.todo +117 -0
  145. package/apps/agents-server/src/message-providers/email/_common/utils/parseEmailAddresses.ts +19 -0
  146. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.test.ts.todo +119 -0
  147. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddress.ts +19 -0
  148. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.test.ts.todo +74 -0
  149. package/apps/agents-server/src/message-providers/email/_common/utils/stringifyEmailAddresses.ts +14 -0
  150. package/apps/agents-server/src/message-providers/email/sendgrid/SendgridMessageProvider.ts +44 -0
  151. package/apps/agents-server/src/message-providers/email/sendgrid/parseInboundSendgridEmail.ts +49 -0
  152. package/apps/agents-server/src/message-providers/email/zeptomail/ZeptomailMessageProvider.ts +51 -0
  153. package/apps/agents-server/src/message-providers/index.ts +13 -0
  154. package/apps/agents-server/src/message-providers/interfaces/MessageProvider.ts +11 -0
  155. package/apps/agents-server/src/middleware.ts +19 -23
  156. package/apps/agents-server/src/tools/$provideBrowserForServer.ts +32 -0
  157. package/apps/agents-server/src/tools/$provideCdnForServer.ts +7 -2
  158. package/apps/agents-server/src/utils/auth.ts +117 -17
  159. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +57 -0
  160. package/apps/agents-server/src/utils/cdn/classes/VercelBlobStorage.ts +4 -0
  161. package/apps/agents-server/src/utils/cdn/interfaces/IFilesStorage.ts +18 -0
  162. package/apps/agents-server/src/utils/content/extractBodyContentFromHtml.ts +19 -0
  163. package/apps/agents-server/src/utils/getUserIdFromRequest.ts +35 -0
  164. package/apps/agents-server/src/utils/handleChatCompletion.ts +65 -5
  165. package/apps/agents-server/src/utils/messages/sendMessage.ts +91 -0
  166. package/apps/agents-server/src/utils/messagesAdmin.ts +72 -0
  167. package/apps/agents-server/src/utils/normalization/filenameToPrompt.test.ts +36 -0
  168. package/apps/agents-server/src/utils/normalization/filenameToPrompt.ts +25 -0
  169. package/apps/agents-server/src/utils/validateApiKey.ts +7 -11
  170. package/esm/index.es.js +1534 -1330
  171. package/esm/index.es.js.map +1 -1
  172. package/esm/typings/servers.d.ts +8 -0
  173. package/esm/typings/src/_packages/core.index.d.ts +2 -0
  174. package/esm/typings/src/_packages/types.index.d.ts +16 -2
  175. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +29 -1
  176. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirements.d.ts +6 -6
  177. package/esm/typings/src/book-2.0/agent-source/createAgentModelRequirementsWithCommitments.closed.test.d.ts +1 -0
  178. package/esm/typings/src/book-2.0/utils/generatePlaceholderAgentProfileImageUrl.d.ts +3 -3
  179. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +5 -1
  180. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +5 -0
  181. package/esm/typings/src/book-components/Chat/CodeBlock/CodeBlock.d.ts +13 -0
  182. package/esm/typings/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  183. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +9 -13
  184. package/esm/typings/src/book-components/_common/Dropdown/Dropdown.d.ts +3 -3
  185. package/esm/typings/src/book-components/_common/HamburgerMenu/HamburgerMenu.d.ts +1 -1
  186. package/esm/typings/src/book-components/_common/MenuHoisting/MenuHoistingContext.d.ts +56 -0
  187. package/esm/typings/src/book-components/icons/AboutIcon.d.ts +1 -1
  188. package/esm/typings/src/book-components/icons/AttachmentIcon.d.ts +1 -1
  189. package/esm/typings/src/book-components/icons/CameraIcon.d.ts +1 -1
  190. package/esm/typings/src/book-components/icons/DownloadIcon.d.ts +1 -1
  191. package/esm/typings/src/book-components/icons/MenuIcon.d.ts +1 -1
  192. package/esm/typings/src/book-components/icons/SaveIcon.d.ts +1 -1
  193. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +22 -12
  194. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +27 -15
  195. package/esm/typings/src/commitments/DICTIONARY/DICTIONARY.d.ts +46 -0
  196. package/esm/typings/src/commitments/index.d.ts +2 -1
  197. package/esm/typings/src/llm-providers/_common/utils/count-total-usage/countUsage.d.ts +1 -1
  198. package/esm/typings/src/llm-providers/_multiple/MultipleLlmExecutionTools.d.ts +6 -2
  199. package/esm/typings/src/llm-providers/agent/Agent.d.ts +6 -1
  200. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +1 -1
  201. package/esm/typings/src/llm-providers/ollama/OllamaExecutionTools.d.ts +1 -1
  202. package/esm/typings/src/llm-providers/openai/createOpenAiCompatibleExecutionTools.d.ts +1 -1
  203. package/esm/typings/src/llm-providers/remote/RemoteLlmExecutionTools.d.ts +1 -0
  204. package/esm/typings/src/remote-server/ui/ServerApp.d.ts +1 -1
  205. package/esm/typings/src/search-engines/SearchEngine.d.ts +9 -0
  206. package/esm/typings/src/search-engines/SearchResult.d.ts +18 -0
  207. package/esm/typings/src/search-engines/bing/BingSearchEngine.d.ts +15 -0
  208. package/esm/typings/src/search-engines/dummy/DummySearchEngine.d.ts +15 -0
  209. package/esm/typings/src/types/Message.d.ts +49 -0
  210. package/esm/typings/src/types/ModelRequirements.d.ts +38 -14
  211. package/esm/typings/src/types/typeAliases.d.ts +23 -1
  212. package/esm/typings/src/utils/color/utils/colorToDataUrl.d.ts +2 -1
  213. package/esm/typings/src/utils/environment/$detectRuntimeEnvironment.d.ts +4 -4
  214. package/esm/typings/src/utils/environment/$isRunningInBrowser.d.ts +1 -1
  215. package/esm/typings/src/utils/environment/$isRunningInJest.d.ts +1 -1
  216. package/esm/typings/src/utils/environment/$isRunningInNode.d.ts +1 -1
  217. package/esm/typings/src/utils/environment/$isRunningInWebWorker.d.ts +1 -1
  218. package/esm/typings/src/utils/markdown/extractAllBlocksFromMarkdown.d.ts +2 -2
  219. package/esm/typings/src/utils/markdown/extractOneBlockFromMarkdown.d.ts +2 -2
  220. package/esm/typings/src/utils/random/$randomAgentPersona.d.ts +3 -2
  221. package/esm/typings/src/utils/random/$randomBase58.d.ts +12 -0
  222. package/esm/typings/src/version.d.ts +1 -1
  223. package/package.json +1 -1
  224. package/umd/index.umd.js +1542 -1338
  225. package/umd/index.umd.js.map +1 -1
  226. package/apps/agents-server/package-lock.json +0 -27
  227. package/apps/agents-server/public/fonts/download-font.js +0 -22
  228. package/apps/agents-server/src/components/PrintButton/PrintButton.tsx +0 -18
  229. package/esm/typings/src/book-2.0/utils/generateGravatarUrl.d.ts +0 -10
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('waitasecond'), require('prompts'), require('path'), require('fs/promises'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto'), require('socket.io-client'), require('rxjs'), require('child_process'), require('jszip'), require('papaparse'), require('crypto-js'), require('mime-types'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('openai'), require('@mozilla/readability'), require('jsdom'), require('showdown')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'waitasecond', 'prompts', 'path', 'fs/promises', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto', 'socket.io-client', 'rxjs', 'child_process', 'jszip', 'papaparse', 'crypto-js', 'mime-types', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'openai', '@mozilla/readability', 'jsdom', 'showdown'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global.spaceTrim$1, global.waitasecond, global.prompts, global.path, global.promises, global.dotenv, global.hexEncoder, global.sha256, global.crypto, global.socket_ioClient, global.rxjs, global.child_process, global.JSZip, global.papaparse, global.cryptoJs, global.mimeTypes, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.OpenAI, global.readability, global.jsdom, global.showdown));
5
- })(this, (function (exports, colors, commander, spaceTrim$1, waitasecond, prompts, path, promises, dotenv, hexEncoder, sha256, crypto, socket_ioClient, rxjs, child_process, JSZip, papaparse, cryptoJs, mimeTypes, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, OpenAI, readability, jsdom, showdown) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('waitasecond'), require('prompts'), require('path'), require('fs/promises'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto'), require('socket.io-client'), require('crypto-js'), require('rxjs'), require('child_process'), require('jszip'), require('papaparse'), require('mime-types'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('openai'), require('@mozilla/readability'), require('jsdom'), require('showdown')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'waitasecond', 'prompts', 'path', 'fs/promises', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto', 'socket.io-client', 'crypto-js', 'rxjs', 'child_process', 'jszip', 'papaparse', 'mime-types', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'openai', '@mozilla/readability', 'jsdom', 'showdown'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global.spaceTrim$1, global.waitasecond, global.prompts, global.path, global.promises, global.dotenv, global.hexEncoder, global.sha256, global.crypto, global.socket_ioClient, global.cryptoJs, global.rxjs, global.child_process, global.JSZip, global.papaparse, global.mimeTypes, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.OpenAI, global.readability, global.jsdom, global.showdown));
5
+ })(this, (function (exports, colors, commander, spaceTrim$1, waitasecond, prompts, path, promises, dotenv, hexEncoder, sha256, crypto, socket_ioClient, cryptoJs, rxjs, child_process, JSZip, papaparse, mimeTypes, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, OpenAI, readability, jsdom, showdown) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -56,7 +56,7 @@
56
56
  * @generated
57
57
  * @see https://github.com/webgptorg/promptbook
58
58
  */
59
- const PROMPTBOOK_ENGINE_VERSION = '0.104.0-1';
59
+ const PROMPTBOOK_ENGINE_VERSION = '0.104.0-11';
60
60
  /**
61
61
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
62
62
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -66,6 +66,8 @@
66
66
  * Core Promptbook server configuration.
67
67
  *
68
68
  * This server is also used for auto-federation in the Agents Server.
69
+ *
70
+ * @public exported from `@promptbook/core`
69
71
  */
70
72
  const CORE_SERVER = {
71
73
  title: 'Promptbook Core',
@@ -1382,13 +1384,14 @@
1382
1384
  *
1383
1385
  * @public exported from `@promptbook/utils`
1384
1386
  */
1385
- const $isRunningInNode = new Function(`
1386
- try {
1387
- return this === global;
1388
- } catch (e) {
1389
- return false;
1387
+ function $isRunningInNode() {
1388
+ try {
1389
+ return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
1390
+ }
1391
+ catch (e) {
1392
+ return false;
1393
+ }
1390
1394
  }
1391
- `);
1392
1395
  /**
1393
1396
  * TODO: [🎺]
1394
1397
  */
@@ -1400,13 +1403,14 @@
1400
1403
  *
1401
1404
  * @public exported from `@promptbook/utils`
1402
1405
  */
1403
- const $isRunningInBrowser = new Function(`
1404
- try {
1405
- return this === window;
1406
- } catch (e) {
1407
- return false;
1406
+ function $isRunningInBrowser() {
1407
+ try {
1408
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
1409
+ }
1410
+ catch (e) {
1411
+ return false;
1412
+ }
1408
1413
  }
1409
- `);
1410
1414
  /**
1411
1415
  * TODO: [🎺]
1412
1416
  */
@@ -1418,13 +1422,15 @@
1418
1422
  *
1419
1423
  * @public exported from `@promptbook/utils`
1420
1424
  */
1421
- const $isRunningInJest = new Function(`
1422
- try {
1423
- return process.env.JEST_WORKER_ID !== undefined;
1424
- } catch (e) {
1425
- return false;
1425
+ function $isRunningInJest() {
1426
+ var _a;
1427
+ try {
1428
+ return typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.JEST_WORKER_ID) !== undefined;
1429
+ }
1430
+ catch (e) {
1431
+ return false;
1432
+ }
1426
1433
  }
1427
- `);
1428
1434
  /**
1429
1435
  * TODO: [🎺]
1430
1436
  */
@@ -1436,17 +1442,17 @@
1436
1442
  *
1437
1443
  * @public exported from `@promptbook/utils`
1438
1444
  */
1439
- const $isRunningInWebWorker = new Function(`
1440
- try {
1441
- if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
1442
- return true;
1443
- } else {
1445
+ function $isRunningInWebWorker() {
1446
+ try {
1447
+ // Note: Check for importScripts which is specific to workers
1448
+ // and not available in the main browser thread
1449
+ return (typeof self !== 'undefined' &&
1450
+ typeof self.importScripts === 'function');
1451
+ }
1452
+ catch (e) {
1444
1453
  return false;
1445
1454
  }
1446
- } catch (e) {
1447
- return false;
1448
1455
  }
1449
- `);
1450
1456
  /**
1451
1457
  * TODO: [🎺]
1452
1458
  */
@@ -1964,7 +1970,7 @@
1964
1970
  ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
1965
1971
  ${morePieces.join('; ')}
1966
1972
  `);
1967
- if ($isRunningInNode) {
1973
+ if ($isRunningInNode()) {
1968
1974
  if (isInstalled && isFullyConfigured) {
1969
1975
  providerMessage = colors__default["default"].green(providerMessage);
1970
1976
  }
@@ -3726,6 +3732,7 @@
3726
3732
  }
3727
3733
  }
3728
3734
  /**
3735
+ * TODO: !!!! Deprecate pipeline server and all of its components
3729
3736
  * TODO: Maybe use `$exportJson`
3730
3737
  * TODO: [🧠][🛍] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
3731
3738
  * TODO: [🍓] Allow to list compatible models with each variant
@@ -3735,280 +3742,1166 @@
3735
3742
  */
3736
3743
 
3737
3744
  /**
3738
- * Function isValidJsonString will tell you if the string is valid JSON or not
3745
+ * Normalizes a given text to camelCase format.
3739
3746
  *
3740
- * @param value The string to check
3741
- * @returns `true` if the string is a valid JSON string, false otherwise
3747
+ * Note: [🔂] This function is idempotent.
3742
3748
  *
3749
+ * @param text The text to be normalized.
3750
+ * @param _isFirstLetterCapital Whether the first letter should be capitalized.
3751
+ * @returns The camelCase formatted string.
3752
+ * @example 'helloWorld'
3753
+ * @example 'iLovePromptbook'
3743
3754
  * @public exported from `@promptbook/utils`
3744
3755
  */
3745
- function isValidJsonString(value /* <- [👨‍⚖️] */) {
3746
- try {
3747
- JSON.parse(value);
3748
- return true;
3749
- }
3750
- catch (error) {
3751
- assertsError(error);
3752
- if (error.message.includes('Unexpected token')) {
3753
- return false;
3756
+ function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
3757
+ let charType;
3758
+ let lastCharType = null;
3759
+ let normalizedName = '';
3760
+ for (const char of text) {
3761
+ let normalizedChar;
3762
+ if (/^[a-z]$/.test(char)) {
3763
+ charType = 'LOWERCASE';
3764
+ normalizedChar = char;
3754
3765
  }
3755
- return false;
3766
+ else if (/^[A-Z]$/.test(char)) {
3767
+ charType = 'UPPERCASE';
3768
+ normalizedChar = char.toLowerCase();
3769
+ }
3770
+ else if (/^[0-9]$/.test(char)) {
3771
+ charType = 'NUMBER';
3772
+ normalizedChar = char;
3773
+ }
3774
+ else {
3775
+ charType = 'OTHER';
3776
+ normalizedChar = '';
3777
+ }
3778
+ if (!lastCharType) {
3779
+ if (_isFirstLetterCapital) {
3780
+ normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
3781
+ }
3782
+ }
3783
+ else if (charType !== lastCharType &&
3784
+ !(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
3785
+ !(lastCharType === 'NUMBER') &&
3786
+ !(charType === 'NUMBER')) {
3787
+ normalizedChar = normalizedChar.toUpperCase(); //TODO: [🌺] DRY
3788
+ }
3789
+ normalizedName += normalizedChar;
3790
+ lastCharType = charType;
3756
3791
  }
3792
+ return normalizedName;
3757
3793
  }
3758
-
3759
3794
  /**
3760
- * Makes first letter of a string uppercase
3761
- *
3762
- * Note: [🔂] This function is idempotent.
3763
- *
3764
- * @public exported from `@promptbook/utils`
3795
+ * TODO: [🌺] Use some intermediate util splitWords
3765
3796
  */
3766
- function capitalize(word) {
3767
- return word.substring(0, 1).toUpperCase() + word.substring(1);
3768
- }
3769
3797
 
3770
3798
  /**
3771
- * Extracts all code blocks from markdown.
3799
+ * Creates a Mermaid graph based on the promptbook
3772
3800
  *
3773
- * Note: There are multiple similar functions:
3774
- * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
3775
- * - `extractJsonBlock` extracts exactly one valid JSON code block
3776
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
3777
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
3801
+ * Note: The result is not wrapped in a Markdown code block
3778
3802
  *
3779
- * @param markdown any valid markdown
3780
- * @returns code blocks with language and content
3781
- * @throws {ParseError} if block is not closed properly
3782
- * @public exported from `@promptbook/markdown-utils`
3803
+ * @public exported from `@promptbook/utils`
3783
3804
  */
3784
- function extractAllBlocksFromMarkdown(markdown) {
3785
- const codeBlocks = [];
3786
- const lines = markdown.split('\n');
3787
- // Note: [0] Ensure that the last block notated by gt > will be closed
3788
- lines.push('');
3789
- let currentCodeBlock = null;
3790
- for (const line of lines) {
3791
- if (line.startsWith('> ') || line === '>') {
3792
- if (currentCodeBlock === null) {
3793
- currentCodeBlock = { blockNotation: '>', language: null, content: '' };
3794
- } /* not else */
3795
- if (currentCodeBlock.blockNotation === '>') {
3796
- if (currentCodeBlock.content !== '') {
3797
- currentCodeBlock.content += '\n';
3798
- }
3799
- currentCodeBlock.content += line.slice(2);
3800
- }
3805
+ function renderPromptbookMermaid(pipelineJson, options) {
3806
+ const { linkTask = () => null } = options || {};
3807
+ const MERMAID_PREFIX = 'pipeline_';
3808
+ const MERMAID_KNOWLEDGE_NAME = MERMAID_PREFIX + 'knowledge';
3809
+ const MERMAID_RESERVED_NAME = MERMAID_PREFIX + 'reserved';
3810
+ const MERMAID_INPUT_NAME = MERMAID_PREFIX + 'input';
3811
+ const MERMAID_OUTPUT_NAME = MERMAID_PREFIX + 'output';
3812
+ const parameterNameToTaskName = (parameterName) => {
3813
+ if (parameterName === 'knowledge') {
3814
+ return MERMAID_KNOWLEDGE_NAME;
3801
3815
  }
3802
- else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '>' /* <- Note: [0] */) {
3803
- codeBlocks.push(currentCodeBlock);
3804
- currentCodeBlock = null;
3816
+ else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
3817
+ return MERMAID_RESERVED_NAME;
3805
3818
  }
3806
- /* not else */
3807
- if (line.startsWith('```')) {
3808
- const language = line.slice(3).trim() || null;
3809
- if (currentCodeBlock === null) {
3810
- currentCodeBlock = { blockNotation: '```', language, content: '' };
3811
- }
3812
- else {
3813
- if (language !== null) {
3814
- throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed and already opening new ${language} code block`);
3815
- }
3816
- codeBlocks.push(currentCodeBlock);
3817
- currentCodeBlock = null;
3818
- }
3819
+ const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
3820
+ if (!parameter) {
3821
+ throw new UnexpectedError(`Could not find {${parameterName}}`);
3822
+ // <- TODO: This causes problems when {knowledge} and other reserved parameters are used
3819
3823
  }
3820
- else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '```') {
3821
- if (currentCodeBlock.content !== '') {
3822
- currentCodeBlock.content += '\n';
3823
- }
3824
- currentCodeBlock.content += line.split('\\`\\`\\`').join('```') /* <- TODO: Maybe make proper unescape */;
3824
+ if (parameter.isInput) {
3825
+ return MERMAID_INPUT_NAME;
3825
3826
  }
3826
- }
3827
- if (currentCodeBlock !== null) {
3828
- throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed at the end of the markdown`);
3829
- }
3830
- return codeBlocks;
3827
+ const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName);
3828
+ if (!task) {
3829
+ throw new Error(`Could not find task for {${parameterName}}`);
3830
+ }
3831
+ return MERMAID_PREFIX + (task.name || normalizeTo_camelCase('task-' + titleToName(task.title)));
3832
+ };
3833
+ const inputAndIntermediateParametersMermaid = pipelineJson.tasks
3834
+ .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
3835
+ `${parameterNameToTaskName(resultingParameterName)}("${title}")`,
3836
+ ...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`),
3837
+ ])
3838
+ .join('\n');
3839
+ const outputParametersMermaid = pipelineJson.parameters
3840
+ .filter(({ isOutput }) => isOutput)
3841
+ .map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->${MERMAID_OUTPUT_NAME}`)
3842
+ .join('\n');
3843
+ const linksMermaid = pipelineJson.tasks
3844
+ .map((task) => {
3845
+ const link = linkTask(task);
3846
+ if (link === null) {
3847
+ return '';
3848
+ }
3849
+ const { href, title } = link;
3850
+ const taskName = parameterNameToTaskName(task.resultingParameterName);
3851
+ return `click ${taskName} href "${href}" "${title}";`;
3852
+ })
3853
+ .filter((line) => line !== '')
3854
+ .join('\n');
3855
+ const interactionPointsMermaid = Object.entries({
3856
+ [MERMAID_INPUT_NAME]: 'Input',
3857
+ [MERMAID_OUTPUT_NAME]: 'Output',
3858
+ [MERMAID_RESERVED_NAME]: 'Other',
3859
+ [MERMAID_KNOWLEDGE_NAME]: 'Knowledge',
3860
+ })
3861
+ .filter(([MERMAID_NAME]) => (inputAndIntermediateParametersMermaid + outputParametersMermaid).includes(MERMAID_NAME))
3862
+ .map(([MERMAID_NAME, title]) => `${MERMAID_NAME}((${title})):::${MERMAID_NAME}`)
3863
+ .join('\n');
3864
+ const promptbookMermaid = spaceTrim$1.spaceTrim((block) => `
3865
+
3866
+ %% 🔮 Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually
3867
+
3868
+ flowchart LR
3869
+ subgraph "${pipelineJson.title}"
3870
+
3871
+ %% Basic configuration
3872
+ direction TB
3873
+
3874
+ %% Interaction points from pipeline to outside
3875
+ ${block(interactionPointsMermaid)}
3876
+
3877
+ %% Input and intermediate parameters
3878
+ ${block(inputAndIntermediateParametersMermaid)}
3879
+
3880
+
3881
+ %% Output parameters
3882
+ ${block(outputParametersMermaid)}
3883
+
3884
+ %% Links
3885
+ ${block(linksMermaid)}
3886
+
3887
+ %% Styles
3888
+ classDef ${MERMAID_INPUT_NAME} color: grey;
3889
+ classDef ${MERMAID_OUTPUT_NAME} color: grey;
3890
+ classDef ${MERMAID_RESERVED_NAME} color: grey;
3891
+ classDef ${MERMAID_KNOWLEDGE_NAME} color: grey;
3892
+
3893
+ end;
3894
+
3895
+ `);
3896
+ return promptbookMermaid;
3831
3897
  }
3832
3898
  /**
3833
- * TODO: Maybe name for `blockNotation` instead of '```' and '>'
3899
+ * TODO: [🧠] FOREACH in mermaid graph
3900
+ * TODO: [🧠] Knowledge in mermaid graph
3901
+ * TODO: [🧠] Personas in mermaid graph
3902
+ * TODO: Maybe use some Mermaid package instead of string templating
3903
+ * TODO: [🕌] When more than 2 functionalities, split into separate functions
3834
3904
  */
3835
3905
 
3836
3906
  /**
3837
- * Extracts extracts exactly one valid JSON code block
3838
- *
3839
- * - When given string is a valid JSON as it is, it just returns it
3840
- * - When there is no JSON code block the function throws a `ParseError`
3841
- * - When there are multiple JSON code blocks the function throws a `ParseError`
3842
- *
3843
- * Note: It is not important if marked as ```json BUT if it is VALID JSON
3844
- * Note: There are multiple similar function:
3845
- * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
3846
- * - `extractJsonBlock` extracts exactly one valid JSON code block
3847
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
3848
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
3907
+ * Serializes an error into a [🚉] JSON-serializable object
3849
3908
  *
3850
- * @public exported from `@promptbook/markdown-utils`
3851
- * @throws {ParseError} if there is no valid JSON block in the markdown
3909
+ * @public exported from `@promptbook/utils`
3852
3910
  */
3853
- function extractJsonBlock(markdown) {
3854
- if (isValidJsonString(markdown)) {
3855
- return markdown;
3911
+ function serializeError(error) {
3912
+ const { name, message, stack } = error;
3913
+ const { id } = error;
3914
+ if (!Object.keys(ALL_ERRORS).includes(name)) {
3915
+ console.error(spaceTrim__default["default"]((block) => `
3916
+
3917
+ Cannot serialize error with name "${name}"
3918
+
3919
+ Authors of Promptbook probably forgot to add this error into the list of errors:
3920
+ https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
3921
+
3922
+
3923
+ ${block(stack || message)}
3924
+
3925
+ `));
3926
+ }
3927
+ return {
3928
+ name: name,
3929
+ message,
3930
+ stack,
3931
+ id, // Include id in the serialized object
3932
+ };
3933
+ }
3934
+
3935
+ /**
3936
+ * Async version of Array.forEach
3937
+ *
3938
+ * @param array - Array to iterate over
3939
+ * @param options - Options for the function
3940
+ * @param callbackfunction - Function to call for each item
3941
+ * @public exported from `@promptbook/utils`
3942
+ * @deprecated [🪂] Use queues instead
3943
+ */
3944
+ async function forEachAsync(array, options, callbackfunction) {
3945
+ const { maxParallelCount = Infinity } = options;
3946
+ let index = 0;
3947
+ let runningTasks = [];
3948
+ const tasks = [];
3949
+ for (const item of array) {
3950
+ const currentIndex = index++;
3951
+ const task = callbackfunction(item, currentIndex, array);
3952
+ tasks.push(task);
3953
+ runningTasks.push(task);
3954
+ /* not await */ Promise.resolve(task).then(() => {
3955
+ runningTasks = runningTasks.filter((t) => t !== task);
3956
+ });
3957
+ if (maxParallelCount < runningTasks.length) {
3958
+ await Promise.race(runningTasks);
3959
+ }
3960
+ }
3961
+ await Promise.all(tasks);
3962
+ }
3963
+
3964
+ /**
3965
+ * Function to check if a string is valid CSV
3966
+ *
3967
+ * @param value The string to check
3968
+ * @returns `true` if the string is a valid CSV string, false otherwise
3969
+ *
3970
+ * @public exported from `@promptbook/utils`
3971
+ */
3972
+ function isValidCsvString(value) {
3973
+ try {
3974
+ // A simple check for CSV format: at least one comma and no invalid characters
3975
+ if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
3976
+ return true;
3977
+ }
3978
+ return false;
3979
+ }
3980
+ catch (error) {
3981
+ assertsError(error);
3982
+ return false;
3983
+ }
3984
+ }
3985
+
3986
+ /**
3987
+ * Function isValidJsonString will tell you if the string is valid JSON or not
3988
+ *
3989
+ * @param value The string to check
3990
+ * @returns `true` if the string is a valid JSON string, false otherwise
3991
+ *
3992
+ * @public exported from `@promptbook/utils`
3993
+ */
3994
+ function isValidJsonString(value /* <- [👨‍⚖️] */) {
3995
+ try {
3996
+ JSON.parse(value);
3997
+ return true;
3998
+ }
3999
+ catch (error) {
4000
+ assertsError(error);
4001
+ if (error.message.includes('Unexpected token')) {
4002
+ return false;
4003
+ }
4004
+ return false;
4005
+ }
4006
+ }
4007
+
4008
+ /**
4009
+ * Function to check if a string is valid XML
4010
+ *
4011
+ * @param value
4012
+ * @returns `true` if the string is a valid XML string, false otherwise
4013
+ *
4014
+ * @public exported from `@promptbook/utils`
4015
+ */
4016
+ function isValidXmlString(value) {
4017
+ try {
4018
+ const parser = new DOMParser();
4019
+ const parsedDocument = parser.parseFromString(value, 'application/xml');
4020
+ const parserError = parsedDocument.getElementsByTagName('parsererror');
4021
+ if (parserError.length > 0) {
4022
+ return false;
4023
+ }
4024
+ return true;
4025
+ }
4026
+ catch (error) {
4027
+ assertsError(error);
4028
+ return false;
4029
+ }
4030
+ }
4031
+
4032
+ /**
4033
+ * Format either small or big number
4034
+ *
4035
+ * @public exported from `@promptbook/utils`
4036
+ */
4037
+ function numberToString(value) {
4038
+ if (value === 0) {
4039
+ return '0';
4040
+ }
4041
+ else if (Number.isNaN(value)) {
4042
+ return VALUE_STRINGS.nan;
4043
+ }
4044
+ else if (value === Infinity) {
4045
+ return VALUE_STRINGS.infinity;
4046
+ }
4047
+ else if (value === -Infinity) {
4048
+ return VALUE_STRINGS.negativeInfinity;
4049
+ }
4050
+ for (let exponent = 0; exponent < 15; exponent++) {
4051
+ const factor = 10 ** exponent;
4052
+ const valueRounded = Math.round(value * factor) / factor;
4053
+ if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
4054
+ return valueRounded.toFixed(exponent);
4055
+ }
4056
+ }
4057
+ return value.toString();
4058
+ }
4059
+
4060
+ /**
4061
+ * Function `valueToString` will convert the given value to string
4062
+ * This is useful and used in the `templateParameters` function
4063
+ *
4064
+ * Note: This function is not just calling `toString` method
4065
+ * It's more complex and can handle this conversion specifically for LLM models
4066
+ * See `VALUE_STRINGS`
4067
+ *
4068
+ * Note: There are 2 similar functions
4069
+ * - `valueToString` converts value to string for LLM models as human-readable string
4070
+ * - `asSerializable` converts value to string to preserve full information to be able to convert it back
4071
+ *
4072
+ * @public exported from `@promptbook/utils`
4073
+ */
4074
+ function valueToString(value) {
4075
+ try {
4076
+ if (value === '') {
4077
+ return VALUE_STRINGS.empty;
4078
+ }
4079
+ else if (value === null) {
4080
+ return VALUE_STRINGS.null;
4081
+ }
4082
+ else if (value === undefined) {
4083
+ return VALUE_STRINGS.undefined;
4084
+ }
4085
+ else if (typeof value === 'string') {
4086
+ return value;
4087
+ }
4088
+ else if (typeof value === 'number') {
4089
+ return numberToString(value);
4090
+ }
4091
+ else if (value instanceof Date) {
4092
+ return value.toISOString();
4093
+ }
4094
+ else {
4095
+ try {
4096
+ return JSON.stringify(value);
4097
+ }
4098
+ catch (error) {
4099
+ if (error instanceof TypeError && error.message.includes('circular structure')) {
4100
+ return VALUE_STRINGS.circular;
4101
+ }
4102
+ throw error;
4103
+ }
4104
+ }
4105
+ }
4106
+ catch (error) {
4107
+ assertsError(error);
4108
+ console.error(error);
4109
+ return VALUE_STRINGS.unserializable;
4110
+ }
4111
+ }
4112
+
4113
+ /**
4114
+ * Replaces parameters in template with values from parameters object
4115
+ *
4116
+ * Note: This function is not places strings into string,
4117
+ * It's more complex and can handle this operation specifically for LLM models
4118
+ *
4119
+ * @param template the template with parameters in {curly} braces
4120
+ * @param parameters the object with parameters
4121
+ * @returns the template with replaced parameters
4122
+ * @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
4123
+ * @public exported from `@promptbook/utils`
4124
+ */
4125
+ function templateParameters(template, parameters) {
4126
+ for (const [parameterName, parameterValue] of Object.entries(parameters)) {
4127
+ if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
4128
+ throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
4129
+ }
4130
+ else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
4131
+ // TODO: [🍵]
4132
+ throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
4133
+ }
4134
+ }
4135
+ let replacedTemplates = template;
4136
+ let match;
4137
+ let loopLimit = LOOP_LIMIT;
4138
+ while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
4139
+ .exec(replacedTemplates))) {
4140
+ if (loopLimit-- < 0) {
4141
+ throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
4142
+ }
4143
+ const precol = match.groups.precol;
4144
+ const parameterName = match.groups.parameterName;
4145
+ if (parameterName === '') {
4146
+ // Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
4147
+ continue;
4148
+ }
4149
+ if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
4150
+ throw new PipelineExecutionError('Parameter is already opened or not closed');
4151
+ }
4152
+ if (parameters[parameterName] === undefined) {
4153
+ throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
4154
+ }
4155
+ let parameterValue = parameters[parameterName];
4156
+ if (parameterValue === undefined) {
4157
+ throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
4158
+ }
4159
+ parameterValue = valueToString(parameterValue);
4160
+ // Escape curly braces in parameter values to prevent prompt-injection
4161
+ parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
4162
+ if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
4163
+ parameterValue = parameterValue
4164
+ .split('\n')
4165
+ .map((line, index) => (index === 0 ? line : `${precol}${line}`))
4166
+ .join('\n');
4167
+ }
4168
+ replacedTemplates =
4169
+ replacedTemplates.substring(0, match.index + precol.length) +
4170
+ parameterValue +
4171
+ replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
4172
+ }
4173
+ // [💫] Check if there are parameters that are not closed properly
4174
+ if (/{\w+$/.test(replacedTemplates)) {
4175
+ throw new PipelineExecutionError('Parameter is not closed');
4176
+ }
4177
+ // [💫] Check if there are parameters that are not opened properly
4178
+ if (/^\w+}/.test(replacedTemplates)) {
4179
+ throw new PipelineExecutionError('Parameter is not opened');
4180
+ }
4181
+ return replacedTemplates;
4182
+ }
4183
+
4184
+ /**
4185
+ * Number of characters per standard line with 11pt Arial font size.
4186
+ *
4187
+ * @public exported from `@promptbook/utils`
4188
+ */
4189
+ const CHARACTERS_PER_STANDARD_LINE = 63;
4190
+ /**
4191
+ * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
4192
+ *
4193
+ * @public exported from `@promptbook/utils`
4194
+ */
4195
+ const LINES_PER_STANDARD_PAGE = 44;
4196
+ /**
4197
+ * TODO: [🧠] Should be this `constants.ts` or `config.ts`?
4198
+ * Note: [💞] Ignore a discrepancy between file name and entity name
4199
+ */
4200
+
4201
+ /**
4202
+ * Counts number of characters in the text
4203
+ *
4204
+ * @public exported from `@promptbook/utils`
4205
+ */
4206
+ function countCharacters(text) {
4207
+ // Remove null characters
4208
+ text = text.replace(/\0/g, '');
4209
+ // Replace emojis (and also ZWJ sequence) with hyphens
4210
+ text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
4211
+ text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
4212
+ text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
4213
+ return text.length;
4214
+ }
4215
+ /**
4216
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4217
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4218
+ */
4219
+
4220
+ /**
4221
+ * Counts number of lines in the text
4222
+ *
4223
+ * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
4224
+ *
4225
+ * @public exported from `@promptbook/utils`
4226
+ */
4227
+ function countLines(text) {
4228
+ if (text === '') {
4229
+ return 0;
4230
+ }
4231
+ text = text.replace('\r\n', '\n');
4232
+ text = text.replace('\r', '\n');
4233
+ const lines = text.split('\n');
4234
+ return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
4235
+ }
4236
+ /**
4237
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4238
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4239
+ */
4240
+
4241
+ /**
4242
+ * Counts number of pages in the text
4243
+ *
4244
+ * Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
4245
+ *
4246
+ * @public exported from `@promptbook/utils`
4247
+ */
4248
+ function countPages(text) {
4249
+ return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
4250
+ }
4251
+ /**
4252
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4253
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4254
+ */
4255
+
4256
+ /**
4257
+ * Counts number of paragraphs in the text
4258
+ *
4259
+ * @public exported from `@promptbook/utils`
4260
+ */
4261
+ function countParagraphs(text) {
4262
+ return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
4263
+ }
4264
+ /**
4265
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4266
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4267
+ */
4268
+
4269
+ /**
4270
+ * Split text into sentences
4271
+ *
4272
+ * @public exported from `@promptbook/utils`
4273
+ */
4274
+ function splitIntoSentences(text) {
4275
+ return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
4276
+ }
4277
+ /**
4278
+ * Counts number of sentences in the text
4279
+ *
4280
+ * @public exported from `@promptbook/utils`
4281
+ */
4282
+ function countSentences(text) {
4283
+ return splitIntoSentences(text).length;
4284
+ }
4285
+ /**
4286
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4287
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4288
+ */
4289
+
4290
+ /**
4291
+ * Counts number of words in the text
4292
+ *
4293
+ * @public exported from `@promptbook/utils`
4294
+ */
4295
+ function countWords(text) {
4296
+ text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
4297
+ text = removeDiacritics(text);
4298
+ // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
4299
+ text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
4300
+ return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
4301
+ }
4302
+ /**
4303
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4304
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4305
+ * TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
4306
+ */
4307
+
4308
+ /**
4309
+ * Index of all counter functions
4310
+ *
4311
+ * @public exported from `@promptbook/utils`
4312
+ */
4313
+ const CountUtils = {
4314
+ CHARACTERS: countCharacters,
4315
+ WORDS: countWords,
4316
+ SENTENCES: countSentences,
4317
+ PARAGRAPHS: countParagraphs,
4318
+ LINES: countLines,
4319
+ PAGES: countPages,
4320
+ };
4321
+ /**
4322
+ * TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
4323
+ * Note: [💞] Ignore a discrepancy between file name and entity name
4324
+ */
4325
+
4326
+ /**
4327
+ * Simple wrapper `new Date().toISOString()`
4328
+ *
4329
+ * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
4330
+ *
4331
+ * @returns string_date branded type
4332
+ * @public exported from `@promptbook/utils`
4333
+ */
4334
+ function $getCurrentDate() {
4335
+ return new Date().toISOString();
4336
+ }
4337
+
4338
+ /**
4339
+ * Computes SHA-256 hash of the given object
4340
+ *
4341
+ * @public exported from `@promptbook/utils`
4342
+ */
4343
+ function computeHash(value) {
4344
+ return cryptoJs.SHA256(hexEncoder__default["default"].parse(spaceTrim__default["default"](valueToString(value)))).toString( /* hex */);
4345
+ }
4346
+ /**
4347
+ * TODO: [🥬][🥬] Use this ACRY
4348
+ */
4349
+
4350
+ /**
4351
+ * Function parseNumber will parse number from string
4352
+ *
4353
+ * Note: [🔂] This function is idempotent.
4354
+ * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
4355
+ * Note: it also works only with decimal numbers
4356
+ *
4357
+ * @returns parsed number
4358
+ * @throws {ParseError} if the value is not a number
4359
+ *
4360
+ * @public exported from `@promptbook/utils`
4361
+ */
4362
+ function parseNumber(value) {
4363
+ const originalValue = value;
4364
+ if (typeof value === 'number') {
4365
+ value = value.toString(); // <- TODO: Maybe more efficient way to do this
3856
4366
  }
3857
- const codeBlocks = extractAllBlocksFromMarkdown(markdown);
3858
- const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
3859
- if (jsonBlocks.length === 0) {
3860
- throw new Error('There is no valid JSON block in the markdown');
4367
+ if (typeof value !== 'string') {
4368
+ return 0;
4369
+ }
4370
+ value = value.trim();
4371
+ if (value.startsWith('+')) {
4372
+ return parseNumber(value.substring(1));
4373
+ }
4374
+ if (value.startsWith('-')) {
4375
+ const number = parseNumber(value.substring(1));
4376
+ if (number === 0) {
4377
+ return 0; // <- Note: To prevent -0
4378
+ }
4379
+ return -number;
4380
+ }
4381
+ value = value.replace(/,/g, '.');
4382
+ value = value.toUpperCase();
4383
+ if (value === '') {
4384
+ return 0;
4385
+ }
4386
+ if (value === '♾' || value.startsWith('INF')) {
4387
+ return Infinity;
4388
+ }
4389
+ if (value.includes('/')) {
4390
+ const [numerator_, denominator_] = value.split('/');
4391
+ const numerator = parseNumber(numerator_);
4392
+ const denominator = parseNumber(denominator_);
4393
+ if (denominator === 0) {
4394
+ throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
4395
+ }
4396
+ return numerator / denominator;
4397
+ }
4398
+ if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
4399
+ return 0;
4400
+ }
4401
+ if (value.includes('E')) {
4402
+ const [significand, exponent] = value.split('E');
4403
+ return parseNumber(significand) * 10 ** parseNumber(exponent);
4404
+ }
4405
+ if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
4406
+ throw new ParseError(`Unable to parse number from "${originalValue}"`);
4407
+ }
4408
+ const num = parseFloat(value);
4409
+ if (isNaN(num)) {
4410
+ throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
4411
+ }
4412
+ return num;
4413
+ }
4414
+ /**
4415
+ * TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
4416
+ * TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
4417
+ */
4418
+
4419
+ /**
4420
+ * Makes first letter of a string uppercase
4421
+ *
4422
+ * Note: [🔂] This function is idempotent.
4423
+ *
4424
+ * @public exported from `@promptbook/utils`
4425
+ */
4426
+ function capitalize(word) {
4427
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
4428
+ }
4429
+
4430
+ /**
4431
+ * Makes first letter of a string lowercase
4432
+ *
4433
+ * Note: [🔂] This function is idempotent.
4434
+ *
4435
+ * @public exported from `@promptbook/utils`
4436
+ */
4437
+ function decapitalize(word) {
4438
+ return word.substring(0, 1).toLowerCase() + word.substring(1);
4439
+ }
4440
+
4441
+ /**
4442
+ * Parses keywords from a string
4443
+ *
4444
+ * @param {string} input
4445
+ * @returns {Set} of keywords without diacritics in lowercase
4446
+ * @public exported from `@promptbook/utils`
4447
+ */
4448
+ function parseKeywordsFromString(input) {
4449
+ const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
4450
+ .toLowerCase()
4451
+ .split(/[^a-z0-9]+/gs)
4452
+ .filter((value) => value);
4453
+ return new Set(keywords);
4454
+ }
4455
+
4456
+ /**
4457
+ * Converts a name string into a URI-compatible format.
4458
+ *
4459
+ * @param name The string to be converted to a URI-compatible format.
4460
+ * @returns A URI-compatible string derived from the input name.
4461
+ * @example 'Hello World' -> 'hello-world'
4462
+ * @public exported from `@promptbook/utils`
4463
+ */
4464
+ function nameToUriPart(name) {
4465
+ let uriPart = name;
4466
+ uriPart = uriPart.toLowerCase();
4467
+ uriPart = removeDiacritics(uriPart);
4468
+ uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
4469
+ uriPart = uriPart.replace(/^-+/, '');
4470
+ uriPart = uriPart.replace(/-+$/, '');
4471
+ return uriPart;
4472
+ }
4473
+
4474
+ /**
4475
+ * Converts a given name into URI-compatible parts.
4476
+ *
4477
+ * @param name The name to be converted into URI parts.
4478
+ * @returns An array of URI-compatible parts derived from the name.
4479
+ * @example 'Example Name' -> ['example', 'name']
4480
+ * @public exported from `@promptbook/utils`
4481
+ */
4482
+ function nameToUriParts(name) {
4483
+ return nameToUriPart(name)
4484
+ .split('-')
4485
+ .filter((value) => value !== '');
4486
+ }
4487
+
4488
+ /**
4489
+ * Normalizes a given text to PascalCase format.
4490
+ *
4491
+ * Note: [🔂] This function is idempotent.
4492
+ *
4493
+ * @param text @public exported from `@promptbook/utils`
4494
+ * @returns
4495
+ * @example 'HelloWorld'
4496
+ * @example 'ILovePromptbook'
4497
+ * @public exported from `@promptbook/utils`
4498
+ */
4499
+ function normalizeTo_PascalCase(text) {
4500
+ return normalizeTo_camelCase(text, true);
4501
+ }
4502
+
4503
+ /**
4504
+ * Take every whitespace (space, new line, tab) and replace it with a single space
4505
+ *
4506
+ * Note: [🔂] This function is idempotent.
4507
+ *
4508
+ * @public exported from `@promptbook/utils`
4509
+ */
4510
+ function normalizeWhitespaces(sentence) {
4511
+ return sentence.replace(/\s+/gs, ' ').trim();
4512
+ }
4513
+
4514
+ /**
4515
+ * Removes quotes from a string
4516
+ *
4517
+ * Note: [🔂] This function is idempotent.
4518
+ * Tip: This is very useful for post-processing of the result of the LLM model
4519
+ * Note: This function removes only the same quotes from the beginning and the end of the string
4520
+ * Note: There are two similar functions:
4521
+ * - `removeQuotes` which removes only bounding quotes
4522
+ * - `unwrapResult` which removes whole introduce sentence
4523
+ *
4524
+ * @param text optionally quoted text
4525
+ * @returns text without quotes
4526
+ * @public exported from `@promptbook/utils`
4527
+ */
4528
+ function removeQuotes(text) {
4529
+ if (text.startsWith('"') && text.endsWith('"')) {
4530
+ return text.slice(1, -1);
3861
4531
  }
3862
- if (jsonBlocks.length > 1) {
3863
- throw new Error('There are multiple JSON code blocks in the markdown');
4532
+ if (text.startsWith("'") && text.endsWith("'")) {
4533
+ return text.slice(1, -1);
3864
4534
  }
3865
- return jsonBlocks[0].content;
4535
+ return text;
3866
4536
  }
4537
+
3867
4538
  /**
3868
- * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
3869
- * TODO: [🏢] Make this logic part of `JsonFormatParser` or `isValidJsonString`
4539
+ * Adds suffix to the URL
4540
+ *
4541
+ * @public exported from `@promptbook/utils`
3870
4542
  */
4543
+ function suffixUrl(value, suffix) {
4544
+ const baseUrl = value.href.endsWith('/') ? value.href.slice(0, -1) : value.href;
4545
+ const normalizedSuffix = suffix.replace(/\/+/g, '/');
4546
+ return (baseUrl + normalizedSuffix);
4547
+ }
3871
4548
 
3872
4549
  /**
3873
- * Counts number of characters in the text
4550
+ * Removes quotes and optional introduce text from a string
4551
+ *
4552
+ * Tip: This is very useful for post-processing of the result of the LLM model
4553
+ * Note: This function trims the text and removes whole introduce sentence if it is present
4554
+ * Note: There are two similar functions:
4555
+ * - `removeQuotes` which removes only bounding quotes
4556
+ * - `unwrapResult` which removes whole introduce sentence
3874
4557
  *
4558
+ * @param text optionally quoted text
4559
+ * @returns text without quotes
3875
4560
  * @public exported from `@promptbook/utils`
3876
4561
  */
3877
- function countCharacters(text) {
3878
- // Remove null characters
3879
- text = text.replace(/\0/g, '');
3880
- // Replace emojis (and also ZWJ sequence) with hyphens
3881
- text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
3882
- text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
3883
- text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
3884
- return text.length;
4562
+ function unwrapResult(text, options) {
4563
+ const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
4564
+ let trimmedText = text;
4565
+ // Remove leading and trailing spaces and newlines
4566
+ if (isTrimmed) {
4567
+ trimmedText = spaceTrim$1.spaceTrim(trimmedText);
4568
+ }
4569
+ let processedText = trimmedText;
4570
+ if (isIntroduceSentenceRemoved) {
4571
+ const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
4572
+ if (introduceSentenceRegex.test(text)) {
4573
+ // Remove the introduce sentence and quotes by replacing it with an empty string
4574
+ processedText = processedText.replace(introduceSentenceRegex, '');
4575
+ }
4576
+ processedText = spaceTrim$1.spaceTrim(processedText);
4577
+ }
4578
+ if (processedText.length < 3) {
4579
+ return trimmedText;
4580
+ }
4581
+ if (processedText.includes('\n')) {
4582
+ return trimmedText;
4583
+ }
4584
+ // Remove the quotes by extracting the substring without the first and last characters
4585
+ const unquotedText = processedText.slice(1, -1);
4586
+ // Check if the text starts and ends with quotes
4587
+ if ([
4588
+ ['"', '"'],
4589
+ ["'", "'"],
4590
+ ['`', '`'],
4591
+ ['*', '*'],
4592
+ ['_', '_'],
4593
+ ['„', '“'],
4594
+ ['«', '»'] /* <- QUOTES to config */,
4595
+ ].some(([startQuote, endQuote]) => {
4596
+ if (!processedText.startsWith(startQuote)) {
4597
+ return false;
4598
+ }
4599
+ if (!processedText.endsWith(endQuote)) {
4600
+ return false;
4601
+ }
4602
+ if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
4603
+ return false;
4604
+ }
4605
+ if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
4606
+ return false;
4607
+ }
4608
+ return true;
4609
+ })) {
4610
+ return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
4611
+ }
4612
+ else {
4613
+ return processedText;
4614
+ }
3885
4615
  }
3886
4616
  /**
3887
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3888
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4617
+ * TODO: [🧠] Should this also unwrap the (parenthesis)
3889
4618
  */
3890
4619
 
3891
4620
  /**
3892
- * Number of characters per standard line with 11pt Arial font size.
4621
+ * Parses the task and returns the list of all parameter names
3893
4622
  *
4623
+ * @param template the string template with parameters in {curly} braces
4624
+ * @returns the list of parameter names
3894
4625
  * @public exported from `@promptbook/utils`
3895
4626
  */
3896
- const CHARACTERS_PER_STANDARD_LINE = 63;
4627
+ function extractParameterNames(template) {
4628
+ const matches = template.matchAll(/{\w+}/g);
4629
+ const parameterNames = new Set();
4630
+ for (const match of matches) {
4631
+ const parameterName = match[0].slice(1, -1);
4632
+ parameterNames.add(parameterName);
4633
+ }
4634
+ return parameterNames;
4635
+ }
4636
+
3897
4637
  /**
3898
- * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
3899
- *
4638
+ * Recursively converts JSON strings to JSON objects
4639
+
3900
4640
  * @public exported from `@promptbook/utils`
3901
4641
  */
3902
- const LINES_PER_STANDARD_PAGE = 44;
4642
+ function jsonStringsToJsons(object) {
4643
+ if (object === null) {
4644
+ return object;
4645
+ }
4646
+ if (Array.isArray(object)) {
4647
+ return object.map(jsonStringsToJsons);
4648
+ }
4649
+ if (typeof object !== 'object') {
4650
+ return object;
4651
+ }
4652
+ const newObject = { ...object };
4653
+ for (const [key, value] of Object.entries(object)) {
4654
+ if (typeof value === 'string' && isValidJsonString(value)) {
4655
+ newObject[key] = jsonParse(value);
4656
+ }
4657
+ else {
4658
+ newObject[key] = jsonStringsToJsons(value);
4659
+ }
4660
+ }
4661
+ return newObject;
4662
+ }
3903
4663
  /**
3904
- * TODO: [🧠] Should be this `constants.ts` or `config.ts`?
3905
- * Note: [💞] Ignore a discrepancy between file name and entity name
4664
+ * TODO: Type the return type correctly
3906
4665
  */
3907
4666
 
3908
4667
  /**
3909
- * Counts number of lines in the text
3910
- *
3911
- * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
4668
+ * Create difference set of two sets.
3912
4669
  *
4670
+ * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
3913
4671
  * @public exported from `@promptbook/utils`
3914
4672
  */
3915
- function countLines(text) {
3916
- if (text === '') {
3917
- return 0;
4673
+ function difference(a, b, isEqual = (a, b) => a === b) {
4674
+ const diff = new Set();
4675
+ for (const itemA of Array.from(a)) {
4676
+ if (!Array.from(b).some((itemB) => isEqual(itemA, itemB))) {
4677
+ diff.add(itemA);
4678
+ }
3918
4679
  }
3919
- text = text.replace('\r\n', '\n');
3920
- text = text.replace('\r', '\n');
3921
- const lines = text.split('\n');
3922
- return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
4680
+ return diff;
3923
4681
  }
3924
4682
  /**
3925
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3926
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4683
+ * TODO: [🧠][💯] Maybe also implement symmetricDifference
3927
4684
  */
3928
4685
 
3929
4686
  /**
3930
- * Counts number of pages in the text
3931
- *
3932
- * Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
4687
+ * Creates a new set with all elements that are present in either set
3933
4688
  *
4689
+ * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
3934
4690
  * @public exported from `@promptbook/utils`
3935
4691
  */
3936
- function countPages(text) {
3937
- return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
4692
+ function union(...sets) {
4693
+ const union = new Set();
4694
+ for (const set of sets) {
4695
+ for (const item of Array.from(set)) {
4696
+ union.add(item);
4697
+ }
4698
+ }
4699
+ return union;
3938
4700
  }
4701
+
3939
4702
  /**
3940
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3941
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4703
+ * Checks if value is valid email
4704
+ *
4705
+ * @public exported from `@promptbook/utils`
3942
4706
  */
4707
+ function isValidEmail(email) {
4708
+ if (typeof email !== 'string') {
4709
+ return false;
4710
+ }
4711
+ if (email.split('\n').length > 1) {
4712
+ return false;
4713
+ }
4714
+ return /^.+@.+\..+$/.test(email);
4715
+ }
3943
4716
 
3944
4717
  /**
3945
- * Counts number of paragraphs in the text
4718
+ * Checks if the given value is a valid JavaScript identifier name.
3946
4719
  *
4720
+ * @param javascriptName The value to check for JavaScript identifier validity.
4721
+ * @returns `true` if the value is a valid JavaScript name, false otherwise.
3947
4722
  * @public exported from `@promptbook/utils`
3948
4723
  */
3949
- function countParagraphs(text) {
3950
- return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
4724
+ function isValidJavascriptName(javascriptName) {
4725
+ if (typeof javascriptName !== 'string') {
4726
+ return false;
4727
+ }
4728
+ return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
3951
4729
  }
4730
+
3952
4731
  /**
3953
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3954
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4732
+ * Tests if given string is valid semantic version
4733
+ *
4734
+ * Note: There are two similar functions:
4735
+ * - `isValidSemanticVersion` which tests any semantic version
4736
+ * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
4737
+ *
4738
+ * @public exported from `@promptbook/utils`
3955
4739
  */
4740
+ function isValidSemanticVersion(version) {
4741
+ if (typeof version !== 'string') {
4742
+ return false;
4743
+ }
4744
+ if (version.startsWith('0.0.0')) {
4745
+ return false;
4746
+ }
4747
+ return /^\d+\.\d+\.\d+(-\d+)?$/i.test(version);
4748
+ }
3956
4749
 
3957
4750
  /**
3958
- * Split text into sentences
4751
+ * Tests if given string is valid promptbook version
4752
+ * It looks into list of known promptbook versions.
4753
+ *
4754
+ * @see https://www.npmjs.com/package/promptbook?activeTab=versions
4755
+ * Note: When you are using for example promptbook 2.0.0 and there already is promptbook 3.0.0 it don`t know about it.
4756
+ * Note: There are two similar functions:
4757
+ * - `isValidSemanticVersion` which tests any semantic version
4758
+ * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
3959
4759
  *
3960
4760
  * @public exported from `@promptbook/utils`
3961
4761
  */
3962
- function splitIntoSentences(text) {
3963
- return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
4762
+ function isValidPromptbookVersion(version) {
4763
+ if (!isValidSemanticVersion(version)) {
4764
+ return false;
4765
+ }
4766
+ if ( /* version === '1.0.0' || */version === '2.0.0' || version === '3.0.0') {
4767
+ return false;
4768
+ }
4769
+ // <- TODO: [main] !!3 Check isValidPromptbookVersion against PROMPTBOOK_ENGINE_VERSIONS
4770
+ return true;
3964
4771
  }
4772
+
3965
4773
  /**
3966
- * Counts number of sentences in the text
4774
+ * Tests if given string is valid pipeline URL URL.
4775
+ *
4776
+ * Note: There are two similar functions:
4777
+ * - `isValidUrl` which tests any URL
4778
+ * - `isValidPipelineUrl` *(this one)* which tests just pipeline URL
3967
4779
  *
3968
4780
  * @public exported from `@promptbook/utils`
3969
4781
  */
3970
- function countSentences(text) {
3971
- return splitIntoSentences(text).length;
4782
+ function isValidPipelineUrl(url) {
4783
+ if (!isValidUrl(url)) {
4784
+ return false;
4785
+ }
4786
+ if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
4787
+ return false;
4788
+ }
4789
+ if (url.includes('#')) {
4790
+ // TODO: [🐠]
4791
+ return false;
4792
+ }
4793
+ /*
4794
+ Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
4795
+ if (isUrlOnPrivateNetwork(url)) {
4796
+ return false;
4797
+ }
4798
+ */
4799
+ return true;
3972
4800
  }
3973
4801
  /**
3974
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3975
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4802
+ * TODO: [🐠] Maybe more info why the URL is invalid
3976
4803
  */
3977
4804
 
3978
4805
  /**
3979
- * Counts number of words in the text
4806
+ * Extracts all code blocks from markdown.
3980
4807
  *
3981
- * @public exported from `@promptbook/utils`
4808
+ * Note: There are multiple similar functions:
4809
+ * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
4810
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
4811
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
4812
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
4813
+ *
4814
+ * @param markdown any valid markdown
4815
+ * @returns code blocks with language and content
4816
+ * @throws {ParseError} if block is not closed properly
4817
+ * @public exported from `@promptbook/markdown-utils`
3982
4818
  */
3983
- function countWords(text) {
3984
- text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
3985
- text = removeDiacritics(text);
3986
- // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
3987
- text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
3988
- return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
4819
+ function extractAllBlocksFromMarkdown(markdown) {
4820
+ const codeBlocks = [];
4821
+ const lines = markdown.split('\n');
4822
+ // Note: [0] Ensure that the last block notated by gt > will be closed
4823
+ lines.push('');
4824
+ let currentCodeBlock = null;
4825
+ for (const line of lines) {
4826
+ if (line.startsWith('> ') || line === '>') {
4827
+ if (currentCodeBlock === null) {
4828
+ currentCodeBlock = { blockNotation: '>', language: null, content: '' };
4829
+ } /* not else */
4830
+ if (currentCodeBlock.blockNotation === '>') {
4831
+ if (currentCodeBlock.content !== '') {
4832
+ currentCodeBlock.content += '\n';
4833
+ }
4834
+ currentCodeBlock.content += line.slice(2);
4835
+ }
4836
+ }
4837
+ else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '>' /* <- Note: [0] */) {
4838
+ codeBlocks.push(currentCodeBlock);
4839
+ currentCodeBlock = null;
4840
+ }
4841
+ /* not else */
4842
+ if (line.startsWith('```')) {
4843
+ const language = line.slice(3).trim() || null;
4844
+ if (currentCodeBlock === null) {
4845
+ currentCodeBlock = { blockNotation: '```', language, content: '' };
4846
+ }
4847
+ else {
4848
+ if (language !== null) {
4849
+ throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed and already opening new ${language} code block`);
4850
+ }
4851
+ codeBlocks.push(currentCodeBlock);
4852
+ currentCodeBlock = null;
4853
+ }
4854
+ }
4855
+ else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '```') {
4856
+ if (currentCodeBlock.content !== '') {
4857
+ currentCodeBlock.content += '\n';
4858
+ }
4859
+ currentCodeBlock.content += line.split('\\`\\`\\`').join('```') /* <- TODO: Maybe make proper unescape */;
4860
+ }
4861
+ }
4862
+ if (currentCodeBlock !== null) {
4863
+ throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed at the end of the markdown`);
4864
+ }
4865
+ return codeBlocks;
3989
4866
  }
3990
4867
  /**
3991
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3992
- * TODO: [🧠][✌️] Make some Promptbook-native token system
3993
- * TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
4868
+ * TODO: Maybe name for `blockNotation` instead of '```' and '>'
3994
4869
  */
3995
4870
 
3996
4871
  /**
3997
- * Index of all counter functions
4872
+ * Extracts extracts exactly one valid JSON code block
3998
4873
  *
3999
- * @public exported from `@promptbook/utils`
4874
+ * - When given string is a valid JSON as it is, it just returns it
4875
+ * - When there is no JSON code block the function throws a `ParseError`
4876
+ * - When there are multiple JSON code blocks the function throws a `ParseError`
4877
+ *
4878
+ * Note: It is not important if marked as ```json BUT if it is VALID JSON
4879
+ * Note: There are multiple similar function:
4880
+ * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
4881
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
4882
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
4883
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
4884
+ *
4885
+ * @public exported from `@promptbook/markdown-utils`
4886
+ * @throws {ParseError} if there is no valid JSON block in the markdown
4000
4887
  */
4001
- const CountUtils = {
4002
- CHARACTERS: countCharacters,
4003
- WORDS: countWords,
4004
- SENTENCES: countSentences,
4005
- PARAGRAPHS: countParagraphs,
4006
- LINES: countLines,
4007
- PAGES: countPages,
4008
- };
4888
+ function extractJsonBlock(markdown) {
4889
+ if (isValidJsonString(markdown)) {
4890
+ return markdown;
4891
+ }
4892
+ const codeBlocks = extractAllBlocksFromMarkdown(markdown);
4893
+ const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
4894
+ if (jsonBlocks.length === 0) {
4895
+ throw new Error('There is no valid JSON block in the markdown');
4896
+ }
4897
+ if (jsonBlocks.length > 1) {
4898
+ throw new Error('There are multiple JSON code blocks in the markdown');
4899
+ }
4900
+ return jsonBlocks[0].content;
4901
+ }
4009
4902
  /**
4010
- * TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
4011
- * Note: [💞] Ignore a discrepancy between file name and entity name
4903
+ * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
4904
+ * TODO: [🏢] Make this logic part of `JsonFormatParser` or `isValidJsonString`
4012
4905
  */
4013
4906
 
4014
4907
  /**
@@ -4150,35 +5043,6 @@
4150
5043
  }
4151
5044
  }
4152
5045
 
4153
- /**
4154
- * Simple wrapper `new Date().toISOString()`
4155
- *
4156
- * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
4157
- *
4158
- * @returns string_date branded type
4159
- * @public exported from `@promptbook/utils`
4160
- */
4161
- function $getCurrentDate() {
4162
- return new Date().toISOString();
4163
- }
4164
-
4165
- /**
4166
- * Parses the task and returns the list of all parameter names
4167
- *
4168
- * @param template the string template with parameters in {curly} braces
4169
- * @returns the list of parameter names
4170
- * @public exported from `@promptbook/utils`
4171
- */
4172
- function extractParameterNames(template) {
4173
- const matches = template.matchAll(/{\w+}/g);
4174
- const parameterNames = new Set();
4175
- for (const match of matches) {
4176
- const parameterName = match[0].slice(1, -1);
4177
- parameterNames.add(parameterName);
4178
- }
4179
- return parameterNames;
4180
- }
4181
-
4182
5046
  /**
4183
5047
  * Intercepts LLM tools and counts total usage of the tools
4184
5048
  *
@@ -4254,6 +5118,9 @@
4254
5118
  case 'EMBEDDING':
4255
5119
  promptResult = await llmTools.callEmbeddingModel(prompt);
4256
5120
  break variant;
5121
+ case 'IMAGE_GENERATION':
5122
+ promptResult = await llmTools.callImageGenerationModel(prompt);
5123
+ break variant;
4257
5124
  // <- case [🤖]:
4258
5125
  default:
4259
5126
  throw new PipelineExecutionError(`Unknown model variant "${prompt.modelRequirements.modelVariant}"`);
@@ -4290,12 +5157,13 @@
4290
5157
  }
4291
5158
  }
4292
5159
  catch (error) {
5160
+ assertsError(error);
4293
5161
  // If validation throws an unexpected error, don't cache
4294
5162
  shouldCache = false;
4295
5163
  if (isVerbose) {
4296
5164
  console.info('Not caching result due to validation error for key:', key, {
4297
5165
  content: promptResult.content,
4298
- validationError: error instanceof Error ? error.message : String(error),
5166
+ validationError: serializeError(error),
4299
5167
  });
4300
5168
  }
4301
5169
  }
@@ -4341,6 +5209,11 @@
4341
5209
  return /* not await */ callCommonModel(prompt);
4342
5210
  };
4343
5211
  }
5212
+ if (llmTools.callImageGenerationModel !== undefined) {
5213
+ proxyTools.callImageGenerationModel = async (prompt) => {
5214
+ return /* not await */ callCommonModel(prompt);
5215
+ };
5216
+ }
4344
5217
  // <- Note: [🤖]
4345
5218
  return proxyTools;
4346
5219
  }
@@ -4529,6 +5402,15 @@
4529
5402
  return promptResult;
4530
5403
  };
4531
5404
  }
5405
+ if (llmTools.callImageGenerationModel !== undefined) {
5406
+ proxyTools.callImageGenerationModel = async (prompt) => {
5407
+ // console.info('[🚕] callImageGenerationModel through countTotalUsage');
5408
+ const promptResult = await llmTools.callImageGenerationModel(prompt);
5409
+ totalUsage = addUsage(totalUsage, promptResult.usage);
5410
+ spending.next(promptResult.usage);
5411
+ return promptResult;
5412
+ };
5413
+ }
4532
5414
  // <- Note: [🤖]
4533
5415
  return proxyTools;
4534
5416
  }
@@ -4537,7 +5419,7 @@
4537
5419
  * TODO: [🧠] Is there some meaningfull way how to test this util
4538
5420
  * TODO: [🧠][🌯] Maybe a way how to hide ability to `get totalUsage`
4539
5421
  * > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
4540
- * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
5422
+ * TODO: [👷‍♂️] Write a comprehensive manual explaining the construction and usage of LLM tools in the Promptbook ecosystem
4541
5423
  */
4542
5424
 
4543
5425
  /**
@@ -4651,6 +5533,12 @@
4651
5533
  callEmbeddingModel(prompt) {
4652
5534
  return this.callCommonModel(prompt);
4653
5535
  }
5536
+ /**
5537
+ * Calls the best available embedding model
5538
+ */
5539
+ callImageGenerationModel(prompt) {
5540
+ return this.callCommonModel(prompt);
5541
+ }
4654
5542
  // <- Note: [🤖]
4655
5543
  /**
4656
5544
  * Calls the best available model
@@ -4677,6 +5565,11 @@
4677
5565
  continue llm;
4678
5566
  }
4679
5567
  return await llmExecutionTools.callEmbeddingModel(prompt);
5568
+ case 'IMAGE_GENERATION':
5569
+ if (llmExecutionTools.callImageGenerationModel === undefined) {
5570
+ continue llm;
5571
+ }
5572
+ return await llmExecutionTools.callImageGenerationModel(prompt);
4680
5573
  // <- case [🤖]:
4681
5574
  default:
4682
5575
  throw new UnexpectedError(`Unknown model variant "${prompt.modelRequirements.modelVariant}" in ${llmExecutionTools.title}`);
@@ -5001,21 +5894,6 @@
5001
5894
  * TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
5002
5895
  */
5003
5896
 
5004
- /**
5005
- * Checks if value is valid email
5006
- *
5007
- * @public exported from `@promptbook/utils`
5008
- */
5009
- function isValidEmail(email) {
5010
- if (typeof email !== 'string') {
5011
- return false;
5012
- }
5013
- if (email.split('\n').length > 1) {
5014
- return false;
5015
- }
5016
- return /^.+@.+\..+$/.test(email);
5017
- }
5018
-
5019
5897
  /**
5020
5898
  * @private utility of CLI
5021
5899
  */
@@ -5694,113 +6572,39 @@
5694
6572
  }));
5695
6573
  }
5696
6574
  /**
5697
- * Note: [💞] Ignore a discrepancy between file name and entity name
5698
- * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5699
- */
5700
-
5701
- /**
5702
- * Initializes `login` command for Promptbook CLI utilities
5703
- *
5704
- * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
5705
- *
5706
- * @private internal function of `promptbookCli`
5707
- */
5708
- function $initializeLoginCommand(program) {
5709
- const loginCommand = program.command('login');
5710
- loginCommand.description(spaceTrim__default["default"](`
5711
- Login to the remote Promptbook server
5712
- `));
5713
- loginCommand.action(handleActionErrors(async (cliOptions) => {
5714
- // Note: Not interested in return value of this function but the side effect of logging in
5715
- await $provideLlmToolsForCli({
5716
- isLoginloaded: true,
5717
- cliOptions: {
5718
- ...cliOptions,
5719
- strategy: 'REMOTE_SERVER', // <- Note: Overriding strategy to `REMOTE_SERVER`
5720
- // TODO: Do not allow flag `--strategy` in `login` command at all
5721
- },
5722
- });
5723
- return process.exit(0);
5724
- }));
5725
- }
5726
- /**
5727
- * TODO: Implement non-interactive login
5728
- * Note: [💞] Ignore a discrepancy between file name and entity name
5729
- * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5730
- */
5731
-
5732
- /**
5733
- * Tests if given string is valid semantic version
5734
- *
5735
- * Note: There are two similar functions:
5736
- * - `isValidSemanticVersion` which tests any semantic version
5737
- * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
5738
- *
5739
- * @public exported from `@promptbook/utils`
5740
- */
5741
- function isValidSemanticVersion(version) {
5742
- if (typeof version !== 'string') {
5743
- return false;
5744
- }
5745
- if (version.startsWith('0.0.0')) {
5746
- return false;
5747
- }
5748
- return /^\d+\.\d+\.\d+(-\d+)?$/i.test(version);
5749
- }
5750
-
5751
- /**
5752
- * Tests if given string is valid promptbook version
5753
- * It looks into list of known promptbook versions.
5754
- *
5755
- * @see https://www.npmjs.com/package/promptbook?activeTab=versions
5756
- * Note: When you are using for example promptbook 2.0.0 and there already is promptbook 3.0.0 it don`t know about it.
5757
- * Note: There are two similar functions:
5758
- * - `isValidSemanticVersion` which tests any semantic version
5759
- * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
5760
- *
5761
- * @public exported from `@promptbook/utils`
6575
+ * Note: [💞] Ignore a discrepancy between file name and entity name
6576
+ * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5762
6577
  */
5763
- function isValidPromptbookVersion(version) {
5764
- if (!isValidSemanticVersion(version)) {
5765
- return false;
5766
- }
5767
- if ( /* version === '1.0.0' || */version === '2.0.0' || version === '3.0.0') {
5768
- return false;
5769
- }
5770
- // <- TODO: [main] !!3 Check isValidPromptbookVersion against PROMPTBOOK_ENGINE_VERSIONS
5771
- return true;
5772
- }
5773
6578
 
5774
6579
  /**
5775
- * Tests if given string is valid pipeline URL URL.
6580
+ * Initializes `login` command for Promptbook CLI utilities
5776
6581
  *
5777
- * Note: There are two similar functions:
5778
- * - `isValidUrl` which tests any URL
5779
- * - `isValidPipelineUrl` *(this one)* which tests just pipeline URL
6582
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
5780
6583
  *
5781
- * @public exported from `@promptbook/utils`
6584
+ * @private internal function of `promptbookCli`
5782
6585
  */
5783
- function isValidPipelineUrl(url) {
5784
- if (!isValidUrl(url)) {
5785
- return false;
5786
- }
5787
- if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
5788
- return false;
5789
- }
5790
- if (url.includes('#')) {
5791
- // TODO: [🐠]
5792
- return false;
5793
- }
5794
- /*
5795
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
5796
- if (isUrlOnPrivateNetwork(url)) {
5797
- return false;
5798
- }
5799
- */
5800
- return true;
6586
+ function $initializeLoginCommand(program) {
6587
+ const loginCommand = program.command('login');
6588
+ loginCommand.description(spaceTrim__default["default"](`
6589
+ Login to the remote Promptbook server
6590
+ `));
6591
+ loginCommand.action(handleActionErrors(async (cliOptions) => {
6592
+ // Note: Not interested in return value of this function but the side effect of logging in
6593
+ await $provideLlmToolsForCli({
6594
+ isLoginloaded: true,
6595
+ cliOptions: {
6596
+ ...cliOptions,
6597
+ strategy: 'REMOTE_SERVER', // <- Note: Overriding strategy to `REMOTE_SERVER`
6598
+ // TODO: Do not allow flag `--strategy` in `login` command at all
6599
+ },
6600
+ });
6601
+ return process.exit(0);
6602
+ }));
5801
6603
  }
5802
6604
  /**
5803
- * TODO: [🐠] Maybe more info why the URL is invalid
6605
+ * TODO: Implement non-interactive login
6606
+ * Note: [💞] Ignore a discrepancy between file name and entity name
6607
+ * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5804
6608
  */
5805
6609
 
5806
6610
  /**
@@ -6503,65 +7307,6 @@
6503
7307
  * - [♨] Are tasks prepared
6504
7308
  */
6505
7309
 
6506
- /**
6507
- * Serializes an error into a [🚉] JSON-serializable object
6508
- *
6509
- * @public exported from `@promptbook/utils`
6510
- */
6511
- function serializeError(error) {
6512
- const { name, message, stack } = error;
6513
- const { id } = error;
6514
- if (!Object.keys(ALL_ERRORS).includes(name)) {
6515
- console.error(spaceTrim__default["default"]((block) => `
6516
-
6517
- Cannot serialize error with name "${name}"
6518
-
6519
- Authors of Promptbook probably forgot to add this error into the list of errors:
6520
- https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
6521
-
6522
-
6523
- ${block(stack || message)}
6524
-
6525
- `));
6526
- }
6527
- return {
6528
- name: name,
6529
- message,
6530
- stack,
6531
- id, // Include id in the serialized object
6532
- };
6533
- }
6534
-
6535
- /**
6536
- * Recursively converts JSON strings to JSON objects
6537
-
6538
- * @public exported from `@promptbook/utils`
6539
- */
6540
- function jsonStringsToJsons(object) {
6541
- if (object === null) {
6542
- return object;
6543
- }
6544
- if (Array.isArray(object)) {
6545
- return object.map(jsonStringsToJsons);
6546
- }
6547
- if (typeof object !== 'object') {
6548
- return object;
6549
- }
6550
- const newObject = { ...object };
6551
- for (const [key, value] of Object.entries(object)) {
6552
- if (typeof value === 'string' && isValidJsonString(value)) {
6553
- newObject[key] = jsonParse(value);
6554
- }
6555
- else {
6556
- newObject[key] = jsonStringsToJsons(value);
6557
- }
6558
- }
6559
- return newObject;
6560
- }
6561
- /**
6562
- * TODO: Type the return type correctly
6563
- */
6564
-
6565
7310
  /**
6566
7311
  * Asserts that the execution of a Promptbook is successful
6567
7312
  *
@@ -6739,144 +7484,63 @@
6739
7484
  message = `Working on ${current.title}`;
6740
7485
  }
6741
7486
  }
6742
- if (!message) {
6743
- if (errors.length) {
6744
- message = errors[errors.length - 1].message || 'Error';
6745
- }
6746
- else if (warnings.length) {
6747
- message = warnings[warnings.length - 1].message || 'Warning';
6748
- }
6749
- else if (status === 'FINISHED') {
6750
- message = 'Finished';
6751
- }
6752
- else if (status === 'ERROR') {
6753
- message = 'Error';
6754
- }
6755
- else {
6756
- message = 'Running';
6757
- }
6758
- }
6759
- }
6760
- return {
6761
- percent: percent,
6762
- message: message + ' (!!!fallback)',
6763
- };
6764
- },
6765
- get createdAt() {
6766
- return createdAt;
6767
- // <- Note: [1] --||--
6768
- },
6769
- get updatedAt() {
6770
- return updatedAt;
6771
- // <- Note: [1] --||--
6772
- },
6773
- asPromise,
6774
- asObservable() {
6775
- return partialResultSubject.asObservable();
6776
- },
6777
- get errors() {
6778
- return errors;
6779
- // <- Note: [1] --||--
6780
- },
6781
- get warnings() {
6782
- return warnings;
6783
- // <- Note: [1] --||--
6784
- },
6785
- get llmCalls() {
6786
- return [...llmCalls, { foo: '!!! bar' }];
6787
- // <- Note: [1] --||--
6788
- },
6789
- get currentValue() {
6790
- return currentValue;
6791
- // <- Note: [1] --||--
6792
- },
6793
- };
6794
- }
6795
- /**
6796
- * TODO: Maybe allow to terminate the task and add getter `isFinished` or `status`
6797
- * TODO: [🐚] Split into more files and make `PrepareTask` & `RemoteTask` + split the function
6798
- */
6799
-
6800
- /**
6801
- * Format either small or big number
6802
- *
6803
- * @public exported from `@promptbook/utils`
6804
- */
6805
- function numberToString(value) {
6806
- if (value === 0) {
6807
- return '0';
6808
- }
6809
- else if (Number.isNaN(value)) {
6810
- return VALUE_STRINGS.nan;
6811
- }
6812
- else if (value === Infinity) {
6813
- return VALUE_STRINGS.infinity;
6814
- }
6815
- else if (value === -Infinity) {
6816
- return VALUE_STRINGS.negativeInfinity;
6817
- }
6818
- for (let exponent = 0; exponent < 15; exponent++) {
6819
- const factor = 10 ** exponent;
6820
- const valueRounded = Math.round(value * factor) / factor;
6821
- if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
6822
- return valueRounded.toFixed(exponent);
6823
- }
6824
- }
6825
- return value.toString();
6826
- }
6827
-
6828
- /**
6829
- * Function `valueToString` will convert the given value to string
6830
- * This is useful and used in the `templateParameters` function
6831
- *
6832
- * Note: This function is not just calling `toString` method
6833
- * It's more complex and can handle this conversion specifically for LLM models
6834
- * See `VALUE_STRINGS`
6835
- *
6836
- * Note: There are 2 similar functions
6837
- * - `valueToString` converts value to string for LLM models as human-readable string
6838
- * - `asSerializable` converts value to string to preserve full information to be able to convert it back
6839
- *
6840
- * @public exported from `@promptbook/utils`
6841
- */
6842
- function valueToString(value) {
6843
- try {
6844
- if (value === '') {
6845
- return VALUE_STRINGS.empty;
6846
- }
6847
- else if (value === null) {
6848
- return VALUE_STRINGS.null;
6849
- }
6850
- else if (value === undefined) {
6851
- return VALUE_STRINGS.undefined;
6852
- }
6853
- else if (typeof value === 'string') {
6854
- return value;
6855
- }
6856
- else if (typeof value === 'number') {
6857
- return numberToString(value);
6858
- }
6859
- else if (value instanceof Date) {
6860
- return value.toISOString();
6861
- }
6862
- else {
6863
- try {
6864
- return JSON.stringify(value);
6865
- }
6866
- catch (error) {
6867
- if (error instanceof TypeError && error.message.includes('circular structure')) {
6868
- return VALUE_STRINGS.circular;
7487
+ if (!message) {
7488
+ if (errors.length) {
7489
+ message = errors[errors.length - 1].message || 'Error';
7490
+ }
7491
+ else if (warnings.length) {
7492
+ message = warnings[warnings.length - 1].message || 'Warning';
7493
+ }
7494
+ else if (status === 'FINISHED') {
7495
+ message = 'Finished';
7496
+ }
7497
+ else if (status === 'ERROR') {
7498
+ message = 'Error';
7499
+ }
7500
+ else {
7501
+ message = 'Running';
7502
+ }
6869
7503
  }
6870
- throw error;
6871
7504
  }
6872
- }
6873
- }
6874
- catch (error) {
6875
- assertsError(error);
6876
- console.error(error);
6877
- return VALUE_STRINGS.unserializable;
6878
- }
7505
+ return {
7506
+ percent: percent,
7507
+ message: message + ' (!!!fallback)',
7508
+ };
7509
+ },
7510
+ get createdAt() {
7511
+ return createdAt;
7512
+ // <- Note: [1] --||--
7513
+ },
7514
+ get updatedAt() {
7515
+ return updatedAt;
7516
+ // <- Note: [1] --||--
7517
+ },
7518
+ asPromise,
7519
+ asObservable() {
7520
+ return partialResultSubject.asObservable();
7521
+ },
7522
+ get errors() {
7523
+ return errors;
7524
+ // <- Note: [1] --||--
7525
+ },
7526
+ get warnings() {
7527
+ return warnings;
7528
+ // <- Note: [1] --||--
7529
+ },
7530
+ get llmCalls() {
7531
+ return [...llmCalls, { foo: '!!! bar' }];
7532
+ // <- Note: [1] --||--
7533
+ },
7534
+ get currentValue() {
7535
+ return currentValue;
7536
+ // <- Note: [1] --||--
7537
+ },
7538
+ };
6879
7539
  }
7540
+ /**
7541
+ * TODO: Maybe allow to terminate the task and add getter `isFinished` or `status`
7542
+ * TODO: [🐚] Split into more files and make `PrepareTask` & `RemoteTask` + split the function
7543
+ */
6880
7544
 
6881
7545
  /**
6882
7546
  * Parses the given script and returns the list of all used variables that are not defined in the script
@@ -7003,41 +7667,6 @@
7003
7667
  * TODO: [🔣] If script require contentLanguage
7004
7668
  */
7005
7669
 
7006
- /**
7007
- * Create difference set of two sets.
7008
- *
7009
- * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
7010
- * @public exported from `@promptbook/utils`
7011
- */
7012
- function difference(a, b, isEqual = (a, b) => a === b) {
7013
- const diff = new Set();
7014
- for (const itemA of Array.from(a)) {
7015
- if (!Array.from(b).some((itemB) => isEqual(itemA, itemB))) {
7016
- diff.add(itemA);
7017
- }
7018
- }
7019
- return diff;
7020
- }
7021
- /**
7022
- * TODO: [🧠][💯] Maybe also implement symmetricDifference
7023
- */
7024
-
7025
- /**
7026
- * Creates a new set with all elements that are present in either set
7027
- *
7028
- * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
7029
- * @public exported from `@promptbook/utils`
7030
- */
7031
- function union(...sets) {
7032
- const union = new Set();
7033
- for (const set of sets) {
7034
- for (const item of Array.from(set)) {
7035
- union.add(item);
7036
- }
7037
- }
7038
- return union;
7039
- }
7040
-
7041
7670
  /**
7042
7671
  * Contains configuration options for parsing and generating CSV files, such as delimiters and quoting rules.
7043
7672
  *
@@ -7066,28 +7695,6 @@
7066
7695
  return csv;
7067
7696
  }
7068
7697
 
7069
- /**
7070
- * Function to check if a string is valid CSV
7071
- *
7072
- * @param value The string to check
7073
- * @returns `true` if the string is a valid CSV string, false otherwise
7074
- *
7075
- * @public exported from `@promptbook/utils`
7076
- */
7077
- function isValidCsvString(value) {
7078
- try {
7079
- // A simple check for CSV format: at least one comma and no invalid characters
7080
- if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
7081
- return true;
7082
- }
7083
- return false;
7084
- }
7085
- catch (error) {
7086
- assertsError(error);
7087
- return false;
7088
- }
7089
- }
7090
-
7091
7698
  /**
7092
7699
  * Definition for CSV spreadsheet
7093
7700
  *
@@ -7267,30 +7874,6 @@
7267
7874
  * TODO: [🏢] Allow to expect something inside each item of list and other formats
7268
7875
  */
7269
7876
 
7270
- /**
7271
- * Function to check if a string is valid XML
7272
- *
7273
- * @param value
7274
- * @returns `true` if the string is a valid XML string, false otherwise
7275
- *
7276
- * @public exported from `@promptbook/utils`
7277
- */
7278
- function isValidXmlString(value) {
7279
- try {
7280
- const parser = new DOMParser();
7281
- const parsedDocument = parser.parseFromString(value, 'application/xml');
7282
- const parserError = parsedDocument.getElementsByTagName('parsererror');
7283
- if (parserError.length > 0) {
7284
- return false;
7285
- }
7286
- return true;
7287
- }
7288
- catch (error) {
7289
- assertsError(error);
7290
- return false;
7291
- }
7292
- }
7293
-
7294
7877
  /**
7295
7878
  * Definition for XML format
7296
7879
  *
@@ -7380,130 +7963,59 @@
7380
7963
  ${block(Array.from(expectedParameterNames)
7381
7964
  .map((parameterName) => `- {${parameterName}}`)
7382
7965
  .join('\n'))}
7383
-
7384
- Remaining available parameters:
7385
- ${block(Array.from(availableParametersNames)
7386
- .map((parameterName) => `- {${parameterName}}`)
7387
- .join('\n'))}
7388
-
7389
- `));
7390
- }
7391
- const expectedParameterNamesArray = Array.from(expectedParameterNames);
7392
- const availableParametersNamesArray = Array.from(availableParametersNames);
7393
- for (let i = 0; i < expectedParameterNames.size; i++) {
7394
- mappedParameters[expectedParameterNamesArray[i]] = availableParameters[availableParametersNamesArray[i]];
7395
- }
7396
- // Note: [👨‍👨‍👧] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
7397
- Object.freeze(mappedParameters);
7398
- return mappedParameters;
7399
- }
7400
-
7401
- /**
7402
- * Takes an item or an array of items and returns an array of items
7403
- *
7404
- * 1) Any item except array and undefined returns array with that one item (also null)
7405
- * 2) Undefined returns empty array
7406
- * 3) Array returns itself
7407
- *
7408
- * @private internal utility
7409
- */
7410
- function arrayableToArray(input) {
7411
- if (input === undefined) {
7412
- return [];
7413
- }
7414
- if (input instanceof Array) {
7415
- return input;
7416
- }
7417
- return [input];
7418
- }
7419
-
7420
- /**
7421
- * Just returns the given `LlmExecutionTools` or joins multiple into one
7422
- *
7423
- * @public exported from `@promptbook/core`
7424
- */
7425
- function getSingleLlmExecutionTools(oneOrMoreLlmExecutionTools) {
7426
- const _llms = arrayableToArray(oneOrMoreLlmExecutionTools);
7427
- const llmTools = _llms.length === 1
7428
- ? _llms[0]
7429
- : joinLlmExecutionTools('Multiple LLM Providers joined by `getSingleLlmExecutionTools`', ..._llms);
7430
- return llmTools;
7431
- }
7432
- /**
7433
- * TODO: [🙆] `getSingleLlmExecutionTools` vs `joinLlmExecutionTools` - explain difference or pick one
7434
- * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
7435
- */
7436
-
7437
- /**
7438
- * Replaces parameters in template with values from parameters object
7439
- *
7440
- * Note: This function is not places strings into string,
7441
- * It's more complex and can handle this operation specifically for LLM models
7442
- *
7443
- * @param template the template with parameters in {curly} braces
7444
- * @param parameters the object with parameters
7445
- * @returns the template with replaced parameters
7446
- * @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
7447
- * @public exported from `@promptbook/utils`
7448
- */
7449
- function templateParameters(template, parameters) {
7450
- for (const [parameterName, parameterValue] of Object.entries(parameters)) {
7451
- if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
7452
- throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
7453
- }
7454
- else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
7455
- // TODO: [🍵]
7456
- throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
7457
- }
7458
- }
7459
- let replacedTemplates = template;
7460
- let match;
7461
- let loopLimit = LOOP_LIMIT;
7462
- while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
7463
- .exec(replacedTemplates))) {
7464
- if (loopLimit-- < 0) {
7465
- throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
7466
- }
7467
- const precol = match.groups.precol;
7468
- const parameterName = match.groups.parameterName;
7469
- if (parameterName === '') {
7470
- // Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
7471
- continue;
7472
- }
7473
- if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
7474
- throw new PipelineExecutionError('Parameter is already opened or not closed');
7475
- }
7476
- if (parameters[parameterName] === undefined) {
7477
- throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
7478
- }
7479
- let parameterValue = parameters[parameterName];
7480
- if (parameterValue === undefined) {
7481
- throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
7482
- }
7483
- parameterValue = valueToString(parameterValue);
7484
- // Escape curly braces in parameter values to prevent prompt-injection
7485
- parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
7486
- if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
7487
- parameterValue = parameterValue
7488
- .split('\n')
7489
- .map((line, index) => (index === 0 ? line : `${precol}${line}`))
7490
- .join('\n');
7491
- }
7492
- replacedTemplates =
7493
- replacedTemplates.substring(0, match.index + precol.length) +
7494
- parameterValue +
7495
- replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
7966
+
7967
+ Remaining available parameters:
7968
+ ${block(Array.from(availableParametersNames)
7969
+ .map((parameterName) => `- {${parameterName}}`)
7970
+ .join('\n'))}
7971
+
7972
+ `));
7496
7973
  }
7497
- // [💫] Check if there are parameters that are not closed properly
7498
- if (/{\w+$/.test(replacedTemplates)) {
7499
- throw new PipelineExecutionError('Parameter is not closed');
7974
+ const expectedParameterNamesArray = Array.from(expectedParameterNames);
7975
+ const availableParametersNamesArray = Array.from(availableParametersNames);
7976
+ for (let i = 0; i < expectedParameterNames.size; i++) {
7977
+ mappedParameters[expectedParameterNamesArray[i]] = availableParameters[availableParametersNamesArray[i]];
7500
7978
  }
7501
- // [💫] Check if there are parameters that are not opened properly
7502
- if (/^\w+}/.test(replacedTemplates)) {
7503
- throw new PipelineExecutionError('Parameter is not opened');
7979
+ // Note: [👨‍👨‍👧] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
7980
+ Object.freeze(mappedParameters);
7981
+ return mappedParameters;
7982
+ }
7983
+
7984
+ /**
7985
+ * Takes an item or an array of items and returns an array of items
7986
+ *
7987
+ * 1) Any item except array and undefined returns array with that one item (also null)
7988
+ * 2) Undefined returns empty array
7989
+ * 3) Array returns itself
7990
+ *
7991
+ * @private internal utility
7992
+ */
7993
+ function arrayableToArray(input) {
7994
+ if (input === undefined) {
7995
+ return [];
7504
7996
  }
7505
- return replacedTemplates;
7997
+ if (input instanceof Array) {
7998
+ return input;
7999
+ }
8000
+ return [input];
8001
+ }
8002
+
8003
+ /**
8004
+ * Just returns the given `LlmExecutionTools` or joins multiple into one
8005
+ *
8006
+ * @public exported from `@promptbook/core`
8007
+ */
8008
+ function getSingleLlmExecutionTools(oneOrMoreLlmExecutionTools) {
8009
+ const _llms = arrayableToArray(oneOrMoreLlmExecutionTools);
8010
+ const llmTools = _llms.length === 1
8011
+ ? _llms[0]
8012
+ : joinLlmExecutionTools('Multiple LLM Providers joined by `getSingleLlmExecutionTools`', ..._llms);
8013
+ return llmTools;
7506
8014
  }
8015
+ /**
8016
+ * TODO: [🙆] `getSingleLlmExecutionTools` vs `joinLlmExecutionTools` - explain difference or pick one
8017
+ * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
8018
+ */
7507
8019
 
7508
8020
  /**
7509
8021
  * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
@@ -7599,8 +8111,9 @@
7599
8111
  $ongoingTaskResult.$resultString = $ongoingTaskResult.$completionResult.content;
7600
8112
  break variant;
7601
8113
  case 'EMBEDDING':
8114
+ case 'IMAGE_GENERATION':
7602
8115
  throw new PipelineExecutionError(spaceTrim$1.spaceTrim((block) => `
7603
- Embedding model can not be used in pipeline
8116
+ ${modelRequirements.modelVariant} model can not be used in pipeline
7604
8117
 
7605
8118
  This should be catched during parsing
7606
8119
 
@@ -8734,35 +9247,6 @@
8734
9247
  return pipelineExecutor;
8735
9248
  }
8736
9249
 
8737
- /**
8738
- * Async version of Array.forEach
8739
- *
8740
- * @param array - Array to iterate over
8741
- * @param options - Options for the function
8742
- * @param callbackfunction - Function to call for each item
8743
- * @public exported from `@promptbook/utils`
8744
- * @deprecated [🪂] Use queues instead
8745
- */
8746
- async function forEachAsync(array, options, callbackfunction) {
8747
- const { maxParallelCount = Infinity } = options;
8748
- let index = 0;
8749
- let runningTasks = [];
8750
- const tasks = [];
8751
- for (const item of array) {
8752
- const currentIndex = index++;
8753
- const task = callbackfunction(item, currentIndex, array);
8754
- tasks.push(task);
8755
- runningTasks.push(task);
8756
- /* not await */ Promise.resolve(task).then(() => {
8757
- runningTasks = runningTasks.filter((t) => t !== task);
8758
- });
8759
- if (maxParallelCount < runningTasks.length) {
8760
- await Promise.race(runningTasks);
8761
- }
8762
- }
8763
- await Promise.all(tasks);
8764
- }
8765
-
8766
9250
  /**
8767
9251
  * Prepares the persona for the pipeline
8768
9252
  *
@@ -9898,75 +10382,6 @@
9898
10382
  * TODO: [💝] Unite object for expecting amount and format - remove format
9899
10383
  */
9900
10384
 
9901
- /**
9902
- * Function parseNumber will parse number from string
9903
- *
9904
- * Note: [🔂] This function is idempotent.
9905
- * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
9906
- * Note: it also works only with decimal numbers
9907
- *
9908
- * @returns parsed number
9909
- * @throws {ParseError} if the value is not a number
9910
- *
9911
- * @public exported from `@promptbook/utils`
9912
- */
9913
- function parseNumber(value) {
9914
- const originalValue = value;
9915
- if (typeof value === 'number') {
9916
- value = value.toString(); // <- TODO: Maybe more efficient way to do this
9917
- }
9918
- if (typeof value !== 'string') {
9919
- return 0;
9920
- }
9921
- value = value.trim();
9922
- if (value.startsWith('+')) {
9923
- return parseNumber(value.substring(1));
9924
- }
9925
- if (value.startsWith('-')) {
9926
- const number = parseNumber(value.substring(1));
9927
- if (number === 0) {
9928
- return 0; // <- Note: To prevent -0
9929
- }
9930
- return -number;
9931
- }
9932
- value = value.replace(/,/g, '.');
9933
- value = value.toUpperCase();
9934
- if (value === '') {
9935
- return 0;
9936
- }
9937
- if (value === '♾' || value.startsWith('INF')) {
9938
- return Infinity;
9939
- }
9940
- if (value.includes('/')) {
9941
- const [numerator_, denominator_] = value.split('/');
9942
- const numerator = parseNumber(numerator_);
9943
- const denominator = parseNumber(denominator_);
9944
- if (denominator === 0) {
9945
- throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
9946
- }
9947
- return numerator / denominator;
9948
- }
9949
- if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
9950
- return 0;
9951
- }
9952
- if (value.includes('E')) {
9953
- const [significand, exponent] = value.split('E');
9954
- return parseNumber(significand) * 10 ** parseNumber(exponent);
9955
- }
9956
- if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
9957
- throw new ParseError(`Unable to parse number from "${originalValue}"`);
9958
- }
9959
- const num = parseFloat(value);
9960
- if (isNaN(num)) {
9961
- throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
9962
- }
9963
- return num;
9964
- }
9965
- /**
9966
- * TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
9967
- * TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
9968
- */
9969
-
9970
10385
  /**
9971
10386
  import { WrappedError } from '../../errors/WrappedError';
9972
10387
  import { assertsError } from '../../errors/assertsError';
@@ -10109,84 +10524,6 @@
10109
10524
  },
10110
10525
  };
10111
10526
 
10112
- /**
10113
- * Normalizes a given text to camelCase format.
10114
- *
10115
- * Note: [🔂] This function is idempotent.
10116
- *
10117
- * @param text The text to be normalized.
10118
- * @param _isFirstLetterCapital Whether the first letter should be capitalized.
10119
- * @returns The camelCase formatted string.
10120
- * @example 'helloWorld'
10121
- * @example 'iLovePromptbook'
10122
- * @public exported from `@promptbook/utils`
10123
- */
10124
- function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
10125
- let charType;
10126
- let lastCharType = null;
10127
- let normalizedName = '';
10128
- for (const char of text) {
10129
- let normalizedChar;
10130
- if (/^[a-z]$/.test(char)) {
10131
- charType = 'LOWERCASE';
10132
- normalizedChar = char;
10133
- }
10134
- else if (/^[A-Z]$/.test(char)) {
10135
- charType = 'UPPERCASE';
10136
- normalizedChar = char.toLowerCase();
10137
- }
10138
- else if (/^[0-9]$/.test(char)) {
10139
- charType = 'NUMBER';
10140
- normalizedChar = char;
10141
- }
10142
- else {
10143
- charType = 'OTHER';
10144
- normalizedChar = '';
10145
- }
10146
- if (!lastCharType) {
10147
- if (_isFirstLetterCapital) {
10148
- normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
10149
- }
10150
- }
10151
- else if (charType !== lastCharType &&
10152
- !(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
10153
- !(lastCharType === 'NUMBER') &&
10154
- !(charType === 'NUMBER')) {
10155
- normalizedChar = normalizedChar.toUpperCase(); //TODO: [🌺] DRY
10156
- }
10157
- normalizedName += normalizedChar;
10158
- lastCharType = charType;
10159
- }
10160
- return normalizedName;
10161
- }
10162
- /**
10163
- * TODO: [🌺] Use some intermediate util splitWords
10164
- */
10165
-
10166
- /**
10167
- * Removes quotes from a string
10168
- *
10169
- * Note: [🔂] This function is idempotent.
10170
- * Tip: This is very useful for post-processing of the result of the LLM model
10171
- * Note: This function removes only the same quotes from the beginning and the end of the string
10172
- * Note: There are two similar functions:
10173
- * - `removeQuotes` which removes only bounding quotes
10174
- * - `unwrapResult` which removes whole introduce sentence
10175
- *
10176
- * @param text optionally quoted text
10177
- * @returns text without quotes
10178
- * @public exported from `@promptbook/utils`
10179
- */
10180
- function removeQuotes(text) {
10181
- if (text.startsWith('"') && text.endsWith('"')) {
10182
- return text.slice(1, -1);
10183
- }
10184
- if (text.startsWith("'") && text.endsWith("'")) {
10185
- return text.slice(1, -1);
10186
- }
10187
- return text;
10188
- }
10189
-
10190
10527
  /**
10191
10528
  * Function `validateParameterName` will normalize and validate a parameter name for use in pipelines.
10192
10529
  * It removes diacritics, emojis, and quotes, normalizes to camelCase, and checks for reserved names and invalid characters.
@@ -11076,11 +11413,7 @@
11076
11413
  // TODO: [🚜] DRY
11077
11414
  if ($taskJson.modelRequirements[command.key] !== undefined) {
11078
11415
  if ($taskJson.modelRequirements[command.key] === command.value) {
11079
- console.warn(`Multiple commands \`MODEL ${{
11080
- modelName: 'NAME',
11081
- modelVariant: 'VARIANT',
11082
- maxTokens: '???',
11083
- }[command.key]} ${command.value}\` in the task "${$taskJson.title || $taskJson.name}"`);
11416
+ console.warn(`Multiple commands \`MODEL ${command.key} ${command.value}\` in the task "${$taskJson.title || $taskJson.name}"`);
11084
11417
  // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
11085
11418
  }
11086
11419
  else {
@@ -11361,30 +11694,16 @@
11361
11694
  }
11362
11695
  console.warn(spaceTrim__default["default"](`
11363
11696
 
11364
- Persona "${personaName}" is defined multiple times with different description:
11365
-
11366
- First definition:
11367
- ${persona.description}
11368
-
11369
- Second definition:
11370
- ${personaDescription}
11371
-
11372
- `));
11373
- persona.description += spaceTrim__default["default"]('\n\n' + personaDescription);
11374
- }
11375
-
11376
- /**
11377
- * Checks if the given value is a valid JavaScript identifier name.
11378
- *
11379
- * @param javascriptName The value to check for JavaScript identifier validity.
11380
- * @returns `true` if the value is a valid JavaScript name, false otherwise.
11381
- * @public exported from `@promptbook/utils`
11382
- */
11383
- function isValidJavascriptName(javascriptName) {
11384
- if (typeof javascriptName !== 'string') {
11385
- return false;
11386
- }
11387
- return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
11697
+ Persona "${personaName}" is defined multiple times with different description:
11698
+
11699
+ First definition:
11700
+ ${persona.description}
11701
+
11702
+ Second definition:
11703
+ ${personaDescription}
11704
+
11705
+ `));
11706
+ persona.description += spaceTrim__default["default"]('\n\n' + personaDescription);
11388
11707
  }
11389
11708
 
11390
11709
  /**
@@ -12900,345 +13219,59 @@
12900
13219
  if ($pipelineJson.formfactorName === undefined) {
12901
13220
  $pipelineJson.formfactorName = 'GENERIC';
12902
13221
  }
12903
- // =============================================================
12904
- return exportJson({
12905
- name: 'pipelineJson',
12906
- message: `Result of \`parsePipeline\``,
12907
- order: ORDER_OF_PIPELINE_JSON,
12908
- value: {
12909
- formfactorName: 'GENERIC',
12910
- // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
12911
- ...$pipelineJson,
12912
- },
12913
- });
12914
- }
12915
- /**
12916
- * TODO: [🧠] Maybe more things here can be refactored as high-level abstractions
12917
- * TODO: [main] !!4 Warn if used only sync version
12918
- * TODO: [🚞] Report here line/column of error
12919
- * TODO: Use spaceTrim more effectively
12920
- * TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
12921
- * TODO: [🥞] Not optimal parsing because `splitMarkdownIntoSections` is executed twice with same string, once through `flattenMarkdown` and second directly here
12922
- * TODO: [♈] Probably move expectations from tasks to parameters
12923
- * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
12924
- * TODO: [🍙] Make some standard order of json properties
12925
- */
12926
-
12927
- /**
12928
- * Compile pipeline from string (markdown) format to JSON format
12929
- *
12930
- * @see https://github.com/webgptorg/promptbook/discussions/196
12931
- *
12932
- * Note: This function does not validate logic of the pipeline only the parsing
12933
- * Note: This function acts as compilation process
12934
- *
12935
- * @param pipelineString {Promptbook} in string markdown format (.book.md)
12936
- * @param tools - Tools for the preparation and scraping - if not provided together with `llm`, the preparation will be skipped
12937
- * @param options - Options and tools for the compilation
12938
- * @returns {Promptbook} compiled in JSON format (.bookc)
12939
- * @throws {ParseError} if the promptbook string is not valid
12940
- * @public exported from `@promptbook/core`
12941
- */
12942
- async function compilePipeline(pipelineString, tools, options) {
12943
- let pipelineJson = parsePipeline(pipelineString);
12944
- if (tools !== undefined && tools.llm !== undefined) {
12945
- pipelineJson = await preparePipeline(pipelineJson, tools, options || {
12946
- rootDirname: null,
12947
- });
12948
- }
12949
- // Note: No need to use `$exportJson` because `parsePipeline` and `preparePipeline` already do that
12950
- return pipelineJson;
12951
- }
12952
- /**
12953
- * TODO: [🏏] Leverage the batch API and build queues @see https://platform.openai.com/docs/guides/batch
12954
- * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
12955
- * TODO: [🧠] Should be in generated JSON file GENERATOR_WARNING
12956
- */
12957
-
12958
- /**
12959
- * Creates a Mermaid graph based on the promptbook
12960
- *
12961
- * Note: The result is not wrapped in a Markdown code block
12962
- *
12963
- * @public exported from `@promptbook/utils`
12964
- */
12965
- function renderPromptbookMermaid(pipelineJson, options) {
12966
- const { linkTask = () => null } = options || {};
12967
- const MERMAID_PREFIX = 'pipeline_';
12968
- const MERMAID_KNOWLEDGE_NAME = MERMAID_PREFIX + 'knowledge';
12969
- const MERMAID_RESERVED_NAME = MERMAID_PREFIX + 'reserved';
12970
- const MERMAID_INPUT_NAME = MERMAID_PREFIX + 'input';
12971
- const MERMAID_OUTPUT_NAME = MERMAID_PREFIX + 'output';
12972
- const parameterNameToTaskName = (parameterName) => {
12973
- if (parameterName === 'knowledge') {
12974
- return MERMAID_KNOWLEDGE_NAME;
12975
- }
12976
- else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
12977
- return MERMAID_RESERVED_NAME;
12978
- }
12979
- const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
12980
- if (!parameter) {
12981
- throw new UnexpectedError(`Could not find {${parameterName}}`);
12982
- // <- TODO: This causes problems when {knowledge} and other reserved parameters are used
12983
- }
12984
- if (parameter.isInput) {
12985
- return MERMAID_INPUT_NAME;
12986
- }
12987
- const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName);
12988
- if (!task) {
12989
- throw new Error(`Could not find task for {${parameterName}}`);
12990
- }
12991
- return MERMAID_PREFIX + (task.name || normalizeTo_camelCase('task-' + titleToName(task.title)));
12992
- };
12993
- const inputAndIntermediateParametersMermaid = pipelineJson.tasks
12994
- .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
12995
- `${parameterNameToTaskName(resultingParameterName)}("${title}")`,
12996
- ...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`),
12997
- ])
12998
- .join('\n');
12999
- const outputParametersMermaid = pipelineJson.parameters
13000
- .filter(({ isOutput }) => isOutput)
13001
- .map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->${MERMAID_OUTPUT_NAME}`)
13002
- .join('\n');
13003
- const linksMermaid = pipelineJson.tasks
13004
- .map((task) => {
13005
- const link = linkTask(task);
13006
- if (link === null) {
13007
- return '';
13008
- }
13009
- const { href, title } = link;
13010
- const taskName = parameterNameToTaskName(task.resultingParameterName);
13011
- return `click ${taskName} href "${href}" "${title}";`;
13012
- })
13013
- .filter((line) => line !== '')
13014
- .join('\n');
13015
- const interactionPointsMermaid = Object.entries({
13016
- [MERMAID_INPUT_NAME]: 'Input',
13017
- [MERMAID_OUTPUT_NAME]: 'Output',
13018
- [MERMAID_RESERVED_NAME]: 'Other',
13019
- [MERMAID_KNOWLEDGE_NAME]: 'Knowledge',
13020
- })
13021
- .filter(([MERMAID_NAME]) => (inputAndIntermediateParametersMermaid + outputParametersMermaid).includes(MERMAID_NAME))
13022
- .map(([MERMAID_NAME, title]) => `${MERMAID_NAME}((${title})):::${MERMAID_NAME}`)
13023
- .join('\n');
13024
- const promptbookMermaid = spaceTrim$1.spaceTrim((block) => `
13025
-
13026
- %% 🔮 Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually
13027
-
13028
- flowchart LR
13029
- subgraph "${pipelineJson.title}"
13030
-
13031
- %% Basic configuration
13032
- direction TB
13033
-
13034
- %% Interaction points from pipeline to outside
13035
- ${block(interactionPointsMermaid)}
13036
-
13037
- %% Input and intermediate parameters
13038
- ${block(inputAndIntermediateParametersMermaid)}
13039
-
13040
-
13041
- %% Output parameters
13042
- ${block(outputParametersMermaid)}
13043
-
13044
- %% Links
13045
- ${block(linksMermaid)}
13046
-
13047
- %% Styles
13048
- classDef ${MERMAID_INPUT_NAME} color: grey;
13049
- classDef ${MERMAID_OUTPUT_NAME} color: grey;
13050
- classDef ${MERMAID_RESERVED_NAME} color: grey;
13051
- classDef ${MERMAID_KNOWLEDGE_NAME} color: grey;
13052
-
13053
- end;
13054
-
13055
- `);
13056
- return promptbookMermaid;
13057
- }
13058
- /**
13059
- * TODO: [🧠] FOREACH in mermaid graph
13060
- * TODO: [🧠] Knowledge in mermaid graph
13061
- * TODO: [🧠] Personas in mermaid graph
13062
- * TODO: Maybe use some Mermaid package instead of string templating
13063
- * TODO: [🕌] When more than 2 functionalities, split into separate functions
13064
- */
13065
-
13066
- /**
13067
- * Computes SHA-256 hash of the given object
13068
- *
13069
- * @public exported from `@promptbook/utils`
13070
- */
13071
- function computeHash(value) {
13072
- return cryptoJs.SHA256(hexEncoder__default["default"].parse(spaceTrim__default["default"](valueToString(value)))).toString( /* hex */);
13073
- }
13074
- /**
13075
- * TODO: [🥬][🥬] Use this ACRY
13076
- */
13077
-
13078
- /**
13079
- * Makes first letter of a string lowercase
13080
- *
13081
- * Note: [🔂] This function is idempotent.
13082
- *
13083
- * @public exported from `@promptbook/utils`
13084
- */
13085
- function decapitalize(word) {
13086
- return word.substring(0, 1).toLowerCase() + word.substring(1);
13087
- }
13088
-
13089
- /**
13090
- * Parses keywords from a string
13091
- *
13092
- * @param {string} input
13093
- * @returns {Set} of keywords without diacritics in lowercase
13094
- * @public exported from `@promptbook/utils`
13095
- */
13096
- function parseKeywordsFromString(input) {
13097
- const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
13098
- .toLowerCase()
13099
- .split(/[^a-z0-9]+/gs)
13100
- .filter((value) => value);
13101
- return new Set(keywords);
13102
- }
13103
-
13104
- /**
13105
- * Converts a name string into a URI-compatible format.
13106
- *
13107
- * @param name The string to be converted to a URI-compatible format.
13108
- * @returns A URI-compatible string derived from the input name.
13109
- * @example 'Hello World' -> 'hello-world'
13110
- * @public exported from `@promptbook/utils`
13111
- */
13112
- function nameToUriPart(name) {
13113
- let uriPart = name;
13114
- uriPart = uriPart.toLowerCase();
13115
- uriPart = removeDiacritics(uriPart);
13116
- uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
13117
- uriPart = uriPart.replace(/^-+/, '');
13118
- uriPart = uriPart.replace(/-+$/, '');
13119
- return uriPart;
13120
- }
13121
-
13122
- /**
13123
- * Converts a given name into URI-compatible parts.
13124
- *
13125
- * @param name The name to be converted into URI parts.
13126
- * @returns An array of URI-compatible parts derived from the name.
13127
- * @example 'Example Name' -> ['example', 'name']
13128
- * @public exported from `@promptbook/utils`
13129
- */
13130
- function nameToUriParts(name) {
13131
- return nameToUriPart(name)
13132
- .split('-')
13133
- .filter((value) => value !== '');
13134
- }
13135
-
13136
- /**
13137
- * Normalizes a given text to PascalCase format.
13138
- *
13139
- * Note: [🔂] This function is idempotent.
13140
- *
13141
- * @param text @public exported from `@promptbook/utils`
13142
- * @returns
13143
- * @example 'HelloWorld'
13144
- * @example 'ILovePromptbook'
13145
- * @public exported from `@promptbook/utils`
13146
- */
13147
- function normalizeTo_PascalCase(text) {
13148
- return normalizeTo_camelCase(text, true);
13222
+ // =============================================================
13223
+ return exportJson({
13224
+ name: 'pipelineJson',
13225
+ message: `Result of \`parsePipeline\``,
13226
+ order: ORDER_OF_PIPELINE_JSON,
13227
+ value: {
13228
+ formfactorName: 'GENERIC',
13229
+ // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
13230
+ ...$pipelineJson,
13231
+ },
13232
+ });
13149
13233
  }
13150
-
13151
13234
  /**
13152
- * Take every whitespace (space, new line, tab) and replace it with a single space
13153
- *
13154
- * Note: [🔂] This function is idempotent.
13155
- *
13156
- * @public exported from `@promptbook/utils`
13235
+ * TODO: [🧠] Maybe more things here can be refactored as high-level abstractions
13236
+ * TODO: [main] !!4 Warn if used only sync version
13237
+ * TODO: [🚞] Report here line/column of error
13238
+ * TODO: Use spaceTrim more effectively
13239
+ * TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
13240
+ * TODO: [🥞] Not optimal parsing because `splitMarkdownIntoSections` is executed twice with same string, once through `flattenMarkdown` and second directly here
13241
+ * TODO: [♈] Probably move expectations from tasks to parameters
13242
+ * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
13243
+ * TODO: [🍙] Make some standard order of json properties
13157
13244
  */
13158
- function normalizeWhitespaces(sentence) {
13159
- return sentence.replace(/\s+/gs, ' ').trim();
13160
- }
13161
13245
 
13162
13246
  /**
13163
- * Adds suffix to the URL
13247
+ * Compile pipeline from string (markdown) format to JSON format
13164
13248
  *
13165
- * @public exported from `@promptbook/utils`
13166
- */
13167
- function suffixUrl(value, suffix) {
13168
- const baseUrl = value.href.endsWith('/') ? value.href.slice(0, -1) : value.href;
13169
- const normalizedSuffix = suffix.replace(/\/+/g, '/');
13170
- return (baseUrl + normalizedSuffix);
13171
- }
13172
-
13173
- /**
13174
- * Removes quotes and optional introduce text from a string
13249
+ * @see https://github.com/webgptorg/promptbook/discussions/196
13175
13250
  *
13176
- * Tip: This is very useful for post-processing of the result of the LLM model
13177
- * Note: This function trims the text and removes whole introduce sentence if it is present
13178
- * Note: There are two similar functions:
13179
- * - `removeQuotes` which removes only bounding quotes
13180
- * - `unwrapResult` which removes whole introduce sentence
13251
+ * Note: This function does not validate logic of the pipeline only the parsing
13252
+ * Note: This function acts as compilation process
13181
13253
  *
13182
- * @param text optionally quoted text
13183
- * @returns text without quotes
13184
- * @public exported from `@promptbook/utils`
13254
+ * @param pipelineString {Promptbook} in string markdown format (.book.md)
13255
+ * @param tools - Tools for the preparation and scraping - if not provided together with `llm`, the preparation will be skipped
13256
+ * @param options - Options and tools for the compilation
13257
+ * @returns {Promptbook} compiled in JSON format (.bookc)
13258
+ * @throws {ParseError} if the promptbook string is not valid
13259
+ * @public exported from `@promptbook/core`
13185
13260
  */
13186
- function unwrapResult(text, options) {
13187
- const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
13188
- let trimmedText = text;
13189
- // Remove leading and trailing spaces and newlines
13190
- if (isTrimmed) {
13191
- trimmedText = spaceTrim$1.spaceTrim(trimmedText);
13192
- }
13193
- let processedText = trimmedText;
13194
- if (isIntroduceSentenceRemoved) {
13195
- const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
13196
- if (introduceSentenceRegex.test(text)) {
13197
- // Remove the introduce sentence and quotes by replacing it with an empty string
13198
- processedText = processedText.replace(introduceSentenceRegex, '');
13199
- }
13200
- processedText = spaceTrim$1.spaceTrim(processedText);
13201
- }
13202
- if (processedText.length < 3) {
13203
- return trimmedText;
13204
- }
13205
- if (processedText.includes('\n')) {
13206
- return trimmedText;
13207
- }
13208
- // Remove the quotes by extracting the substring without the first and last characters
13209
- const unquotedText = processedText.slice(1, -1);
13210
- // Check if the text starts and ends with quotes
13211
- if ([
13212
- ['"', '"'],
13213
- ["'", "'"],
13214
- ['`', '`'],
13215
- ['*', '*'],
13216
- ['_', '_'],
13217
- ['„', '“'],
13218
- ['«', '»'] /* <- QUOTES to config */,
13219
- ].some(([startQuote, endQuote]) => {
13220
- if (!processedText.startsWith(startQuote)) {
13221
- return false;
13222
- }
13223
- if (!processedText.endsWith(endQuote)) {
13224
- return false;
13225
- }
13226
- if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
13227
- return false;
13228
- }
13229
- if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
13230
- return false;
13231
- }
13232
- return true;
13233
- })) {
13234
- return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
13235
- }
13236
- else {
13237
- return processedText;
13261
+ async function compilePipeline(pipelineString, tools, options) {
13262
+ let pipelineJson = parsePipeline(pipelineString);
13263
+ if (tools !== undefined && tools.llm !== undefined) {
13264
+ pipelineJson = await preparePipeline(pipelineJson, tools, options || {
13265
+ rootDirname: null,
13266
+ });
13238
13267
  }
13268
+ // Note: No need to use `$exportJson` because `parsePipeline` and `preparePipeline` already do that
13269
+ return pipelineJson;
13239
13270
  }
13240
13271
  /**
13241
- * TODO: [🧠] Should this also unwrap the (parenthesis)
13272
+ * TODO: [🏏] Leverage the batch API and build queues @see https://platform.openai.com/docs/guides/batch
13273
+ * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
13274
+ * TODO: [🧠] Should be in generated JSON file GENERATOR_WARNING
13242
13275
  */
13243
13276
 
13244
13277
  /**
@@ -19453,7 +19486,7 @@
19453
19486
  let threadMessages = [];
19454
19487
  if ('thread' in prompt && Array.isArray(prompt.thread)) {
19455
19488
  threadMessages = prompt.thread.map((msg) => ({
19456
- role: msg.role === 'assistant' ? 'assistant' : 'user',
19489
+ role: msg.sender === 'assistant' ? 'assistant' : 'user',
19457
19490
  content: msg.content,
19458
19491
  }));
19459
19492
  }
@@ -19866,13 +19899,14 @@
19866
19899
  const modelName = currentModelRequirements.modelName || this.getDefaultImageGenerationModel().modelName;
19867
19900
  const modelSettings = {
19868
19901
  model: modelName,
19869
- // size: currentModelRequirements.size,
19870
- // quality: currentModelRequirements.quality,
19871
- // style: currentModelRequirements.style,
19902
+ size: currentModelRequirements.size,
19903
+ quality: currentModelRequirements.quality,
19904
+ style: currentModelRequirements.style,
19872
19905
  };
19873
19906
  const rawPromptContent = templateParameters(content, { ...parameters, modelName });
19874
19907
  const rawRequest = {
19875
19908
  ...modelSettings,
19909
+ size: modelSettings.size || '1024x1024',
19876
19910
  prompt: rawPromptContent,
19877
19911
  user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
19878
19912
  response_format: 'url', // TODO: [🧠] Maybe allow b64_json
@@ -20409,10 +20443,10 @@
20409
20443
  // <- TODO: [🛄]
20410
20444
  }
20411
20445
  /**
20412
- * Default model for image generation variant.
20446
+ * Default model for completion variant.
20413
20447
  */
20414
20448
  getDefaultImageGenerationModel() {
20415
- return this.getDefaultModel('!!!'); // <- TODO: [🧠] Pick the best default model
20449
+ return this.getDefaultModel('dall-e-3');
20416
20450
  // <- TODO: [🛄]
20417
20451
  }
20418
20452
  }
@@ -21227,11 +21261,10 @@
21227
21261
  throw new PipelineExecutionError(`${this.title} does not support EMBEDDING model variant`);
21228
21262
  }
21229
21263
  /**
21230
- * Default model for image generation variant.
21264
+ * Default model for completion variant.
21231
21265
  */
21232
21266
  getDefaultImageGenerationModel() {
21233
- return this.getDefaultModel('!!!'); // <- TODO: [🧠] Pick the best default model
21234
- // <- TODO: [🛄]
21267
+ throw new PipelineExecutionError(`${this.title} does not support IMAGE_GENERATION model variant`);
21235
21268
  }
21236
21269
  }
21237
21270
  /**
@@ -23107,6 +23140,114 @@
23107
23140
  * Note: [💞] Ignore a discrepancy between file name and entity name
23108
23141
  */
23109
23142
 
23143
+ /**
23144
+ * DICTIONARY commitment definition
23145
+ *
23146
+ * The DICTIONARY commitment defines specific terms and their meanings that the agent should use correctly
23147
+ * in its reasoning and responses. This ensures consistent terminology usage.
23148
+ *
23149
+ * Key features:
23150
+ * - Multiple DICTIONARY commitments are automatically merged into one
23151
+ * - Content is placed in a dedicated section of the system message
23152
+ * - Terms and definitions are stored in metadata.DICTIONARY for debugging
23153
+ * - Agent should use the defined terms correctly in responses
23154
+ *
23155
+ * Example usage in agent source:
23156
+ *
23157
+ * ```book
23158
+ * Legal Assistant
23159
+ *
23160
+ * PERSONA You are a knowledgeable legal assistant
23161
+ * DICTIONARY Misdemeanor is a minor wrongdoing or criminal offense
23162
+ * DICTIONARY Felony is a serious crime usually punishable by imprisonment for more than one year
23163
+ * DICTIONARY Tort is a civil wrong that causes harm or loss to another person, leading to legal liability
23164
+ * ```
23165
+ *
23166
+ * @private [🪔] Maybe export the commitments through some package
23167
+ */
23168
+ class DictionaryCommitmentDefinition extends BaseCommitmentDefinition {
23169
+ constructor() {
23170
+ super('DICTIONARY');
23171
+ }
23172
+ /**
23173
+ * Short one-line description of DICTIONARY.
23174
+ */
23175
+ get description() {
23176
+ return 'Define terms and their meanings for consistent terminology usage.';
23177
+ }
23178
+ /**
23179
+ * Icon for this commitment.
23180
+ */
23181
+ get icon() {
23182
+ return '📚';
23183
+ }
23184
+ /**
23185
+ * Markdown documentation for DICTIONARY commitment.
23186
+ */
23187
+ get documentation() {
23188
+ return spaceTrim$1.spaceTrim(`
23189
+ # DICTIONARY
23190
+
23191
+ Defines specific terms and their meanings that the agent should use correctly in reasoning and responses.
23192
+
23193
+ ## Key aspects
23194
+
23195
+ - Multiple \`DICTIONARY\` commitments are merged together.
23196
+ - Terms are defined in the format: "Term is definition"
23197
+ - The agent should use these terms consistently in responses.
23198
+ - Definitions help ensure accurate and consistent terminology.
23199
+
23200
+ ## Examples
23201
+
23202
+ \`\`\`book
23203
+ Legal Assistant
23204
+
23205
+ PERSONA You are a knowledgeable legal assistant specializing in criminal law
23206
+ DICTIONARY Misdemeanor is a minor wrongdoing or criminal offense
23207
+ DICTIONARY Felony is a serious crime usually punishable by imprisonment for more than one year
23208
+ DICTIONARY Tort is a civil wrong that causes harm or loss to another person, leading to legal liability
23209
+ \`\`\`
23210
+
23211
+ \`\`\`book
23212
+ Medical Assistant
23213
+
23214
+ PERSONA You are a helpful medical assistant
23215
+ DICTIONARY Hypertension is persistently high blood pressure
23216
+ DICTIONARY Diabetes is a chronic condition that affects how the body processes blood sugar
23217
+ DICTIONARY Vaccine is a biological preparation that provides active immunity to a particular disease
23218
+ \`\`\`
23219
+ `);
23220
+ }
23221
+ applyToAgentModelRequirements(requirements, content) {
23222
+ var _a;
23223
+ const trimmedContent = content.trim();
23224
+ if (!trimmedContent) {
23225
+ return requirements;
23226
+ }
23227
+ // Get existing dictionary entries from metadata
23228
+ const existingDictionary = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.DICTIONARY) || '';
23229
+ // Merge the new dictionary entry with existing entries
23230
+ const mergedDictionary = existingDictionary
23231
+ ? `${existingDictionary}\n${trimmedContent}`
23232
+ : trimmedContent;
23233
+ // Store the merged dictionary in metadata for debugging and inspection
23234
+ const updatedMetadata = {
23235
+ ...requirements.metadata,
23236
+ DICTIONARY: mergedDictionary,
23237
+ };
23238
+ // Create the dictionary section for the system message
23239
+ // Format: "# DICTIONARY\nTerm: definition\nTerm: definition..."
23240
+ const dictionarySection = `# DICTIONARY\n${mergedDictionary}`;
23241
+ return {
23242
+ ...this.appendToSystemMessage(requirements, dictionarySection),
23243
+ metadata: updatedMetadata,
23244
+ };
23245
+ }
23246
+ }
23247
+ /**
23248
+ * Note: [💞] Ignore a discrepancy between file name and entity name
23249
+ */
23250
+
23110
23251
  /**
23111
23252
  * FORMAT commitment definition
23112
23253
  *
@@ -25927,6 +26068,7 @@
25927
26068
  new DeleteCommitmentDefinition('CANCEL'),
25928
26069
  new DeleteCommitmentDefinition('DISCARD'),
25929
26070
  new DeleteCommitmentDefinition('REMOVE'),
26071
+ new DictionaryCommitmentDefinition(),
25930
26072
  new OpenCommitmentDefinition(),
25931
26073
  new ClosedCommitmentDefinition(),
25932
26074
  new UseBrowserCommitmentDefinition(),
@@ -26011,17 +26153,64 @@
26011
26153
  };
26012
26154
  }
26013
26155
  const lines = agentSource.split('\n');
26014
- const agentName = (((_a = lines[0]) === null || _a === void 0 ? void 0 : _a.trim()) || null);
26156
+ let agentName = null;
26157
+ let agentNameLineIndex = -1;
26158
+ // Find the agent name: first non-empty line that is not a commitment and not a horizontal line
26159
+ for (let i = 0; i < lines.length; i++) {
26160
+ const line = lines[i];
26161
+ if (line === undefined) {
26162
+ continue;
26163
+ }
26164
+ const trimmed = line.trim();
26165
+ if (!trimmed) {
26166
+ continue;
26167
+ }
26168
+ const isHorizontal = HORIZONTAL_LINE_PATTERN.test(line);
26169
+ if (isHorizontal) {
26170
+ continue;
26171
+ }
26172
+ let isCommitment = false;
26173
+ for (const definition of COMMITMENT_REGISTRY) {
26174
+ const typeRegex = definition.createTypeRegex();
26175
+ const match = typeRegex.exec(trimmed);
26176
+ if (match && ((_a = match.groups) === null || _a === void 0 ? void 0 : _a.type)) {
26177
+ isCommitment = true;
26178
+ break;
26179
+ }
26180
+ }
26181
+ if (!isCommitment) {
26182
+ agentName = trimmed;
26183
+ agentNameLineIndex = i;
26184
+ break;
26185
+ }
26186
+ }
26015
26187
  const commitments = [];
26016
26188
  const nonCommitmentLines = [];
26017
- // Always add the first line (agent name) to non-commitment lines
26018
- if (lines[0] !== undefined) {
26019
- nonCommitmentLines.push(lines[0]);
26189
+ // Add lines before agentName that are horizontal lines (they are non-commitment)
26190
+ for (let i = 0; i < agentNameLineIndex; i++) {
26191
+ const line = lines[i];
26192
+ if (line === undefined) {
26193
+ continue;
26194
+ }
26195
+ const trimmed = line.trim();
26196
+ if (!trimmed) {
26197
+ continue;
26198
+ }
26199
+ const isHorizontal = HORIZONTAL_LINE_PATTERN.test(line);
26200
+ if (isHorizontal) {
26201
+ nonCommitmentLines.push(line);
26202
+ }
26203
+ // Note: Commitments before agentName are not added to nonCommitmentLines
26204
+ }
26205
+ // Add the agent name line to non-commitment lines
26206
+ if (agentNameLineIndex >= 0) {
26207
+ nonCommitmentLines.push(lines[agentNameLineIndex]);
26020
26208
  }
26021
26209
  // Parse commitments with multiline support
26022
26210
  let currentCommitment = null;
26023
- // Process lines starting from the second line (skip agent name)
26024
- for (let i = 1; i < lines.length; i++) {
26211
+ // Process lines starting from after the agent name line
26212
+ const startIndex = agentNameLineIndex >= 0 ? agentNameLineIndex + 1 : 0;
26213
+ for (let i = startIndex; i < lines.length; i++) {
26025
26214
  const line = lines[i];
26026
26215
  if (line === undefined) {
26027
26216
  continue;
@@ -26241,7 +26430,12 @@
26241
26430
  };
26242
26431
  }
26243
26432
  // Apply each commitment in order using reduce-like pattern
26244
- for (const commitment of filteredCommitments) {
26433
+ for (let i = 0; i < filteredCommitments.length; i++) {
26434
+ const commitment = filteredCommitments[i];
26435
+ // CLOSED commitment should work only if its the last commitment in the book
26436
+ if (commitment.type === 'CLOSED' && i !== filteredCommitments.length - 1) {
26437
+ continue;
26438
+ }
26245
26439
  const definition = getCommitmentDefinition(commitment.type);
26246
26440
  if (definition) {
26247
26441
  try {
@@ -26282,44 +26476,6 @@
26282
26476
  };
26283
26477
  }
26284
26478
 
26285
- /**
26286
- * Generates a gravatar URL based on agent name for fallback avatar
26287
- *
26288
- * @param agentName The agent name to generate avatar for
26289
- * @returns Gravatar URL
26290
- *
26291
- * @private - [🤹] The fact that profile image is Gravatar is just implementation detail which should be hidden for consumer
26292
- */
26293
- function generateGravatarUrl(agentName) {
26294
- // Use a default name if none provided
26295
- const safeName = agentName || 'Anonymous Agent';
26296
- // Create a simple hash from the name for consistent avatar
26297
- let hash = 0;
26298
- for (let i = 0; i < safeName.length; i++) {
26299
- const char = safeName.charCodeAt(i);
26300
- hash = (hash << 5) - hash + char;
26301
- hash = hash & hash; // Convert to 32bit integer
26302
- }
26303
- const avatarId = Math.abs(hash).toString();
26304
- return `https://www.gravatar.com/avatar/${avatarId}?default=robohash&size=200&rating=x`;
26305
- }
26306
-
26307
- /**
26308
- * Generates an image for the agent to use as profile image
26309
- *
26310
- * @param agentName The agent name to generate avatar for
26311
- * @returns The placeholder profile image URL for the agent
26312
- *
26313
- * @public exported from `@promptbook/core`
26314
- */
26315
- function generatePlaceholderAgentProfileImageUrl(agentName) {
26316
- // Note: [🤹] The fact that profile image is Gravatar is just implementation detail which should be hidden for consumer
26317
- return generateGravatarUrl(agentName);
26318
- }
26319
- /**
26320
- * TODO: [🤹] Figure out best placeholder image generator https://i.pravatar.cc/1000?u=568
26321
- */
26322
-
26323
26479
  /**
26324
26480
  * Computes SHA-256 hash of the agent source
26325
26481
  *
@@ -26387,7 +26543,57 @@
26387
26543
  }
26388
26544
  const meta = {};
26389
26545
  const links = [];
26546
+ const capabilities = [];
26390
26547
  for (const commitment of parseResult.commitments) {
26548
+ if (commitment.type === 'USE BROWSER') {
26549
+ capabilities.push({
26550
+ type: 'browser',
26551
+ label: 'Browser',
26552
+ iconName: 'Globe',
26553
+ });
26554
+ continue;
26555
+ }
26556
+ if (commitment.type === 'USE SEARCH ENGINE') {
26557
+ capabilities.push({
26558
+ type: 'search-engine',
26559
+ label: 'Search Internet',
26560
+ iconName: 'Search',
26561
+ });
26562
+ continue;
26563
+ }
26564
+ if (commitment.type === 'KNOWLEDGE') {
26565
+ const content = spaceTrim__default["default"](commitment.content).split('\n')[0] || '';
26566
+ let label = content;
26567
+ let iconName = 'Book';
26568
+ if (content.startsWith('http://') || content.startsWith('https://')) {
26569
+ try {
26570
+ const url = new URL(content);
26571
+ if (url.pathname.endsWith('.pdf')) {
26572
+ label = url.pathname.split('/').pop() || 'Document.pdf';
26573
+ iconName = 'FileText';
26574
+ }
26575
+ else {
26576
+ label = url.hostname.replace(/^www\./, '');
26577
+ }
26578
+ }
26579
+ catch (e) {
26580
+ // Invalid URL, treat as text
26581
+ }
26582
+ }
26583
+ else {
26584
+ // Text content - take first few words
26585
+ const words = content.split(/\s+/);
26586
+ if (words.length > 4) {
26587
+ label = words.slice(0, 4).join(' ') + '...';
26588
+ }
26589
+ }
26590
+ capabilities.push({
26591
+ type: 'knowledge',
26592
+ label,
26593
+ iconName,
26594
+ });
26595
+ continue;
26596
+ }
26391
26597
  if (commitment.type === 'META LINK') {
26392
26598
  const linkValue = spaceTrim__default["default"](commitment.content);
26393
26599
  links.push(linkValue);
@@ -26417,10 +26623,6 @@
26417
26623
  const metaType = normalizeTo_camelCase(metaTypeRaw);
26418
26624
  meta[metaType] = spaceTrim__default["default"](commitment.content.substring(metaTypeRaw.length));
26419
26625
  }
26420
- // Generate gravatar fallback if no meta image specified
26421
- if (!meta.image) {
26422
- meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
26423
- }
26424
26626
  // Generate fullname fallback if no meta fullname specified
26425
26627
  if (!meta.fullname) {
26426
26628
  meta.fullname = parseResult.agentName || createDefaultAgentName(agentSource);
@@ -26432,11 +26634,13 @@
26432
26634
  return {
26433
26635
  agentName: normalizeAgentName(parseResult.agentName || createDefaultAgentName(agentSource)),
26434
26636
  agentHash,
26637
+ permanentId: meta.id,
26435
26638
  personaDescription,
26436
26639
  initialMessage,
26437
26640
  meta,
26438
26641
  links,
26439
26642
  parameters,
26643
+ capabilities,
26440
26644
  };
26441
26645
  }
26442
26646
  /**