@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/esm/index.es.js CHANGED
@@ -10,11 +10,11 @@ import hexEncoder from 'crypto-js/enc-hex';
10
10
  import sha256 from 'crypto-js/sha256';
11
11
  import { randomBytes } from 'crypto';
12
12
  import { io } from 'socket.io-client';
13
+ import { SHA256 } from 'crypto-js';
13
14
  import { Subject } from 'rxjs';
14
15
  import { spawn } from 'child_process';
15
16
  import JSZip from 'jszip';
16
17
  import { parse, unparse } from 'papaparse';
17
- import { SHA256 } from 'crypto-js';
18
18
  import { lookup, extension } from 'mime-types';
19
19
  import glob from 'glob-promise';
20
20
  import moment from 'moment';
@@ -47,7 +47,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
47
47
  * @generated
48
48
  * @see https://github.com/webgptorg/promptbook
49
49
  */
50
- const PROMPTBOOK_ENGINE_VERSION = '0.104.0-1';
50
+ const PROMPTBOOK_ENGINE_VERSION = '0.104.0-11';
51
51
  /**
52
52
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
53
53
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -57,6 +57,8 @@ const PROMPTBOOK_ENGINE_VERSION = '0.104.0-1';
57
57
  * Core Promptbook server configuration.
58
58
  *
59
59
  * This server is also used for auto-federation in the Agents Server.
60
+ *
61
+ * @public exported from `@promptbook/core`
60
62
  */
61
63
  const CORE_SERVER = {
62
64
  title: 'Promptbook Core',
@@ -1373,13 +1375,14 @@ class EnvironmentMismatchError extends Error {
1373
1375
  *
1374
1376
  * @public exported from `@promptbook/utils`
1375
1377
  */
1376
- const $isRunningInNode = new Function(`
1378
+ function $isRunningInNode() {
1377
1379
  try {
1378
- return this === global;
1379
- } catch (e) {
1380
+ return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
1381
+ }
1382
+ catch (e) {
1380
1383
  return false;
1381
1384
  }
1382
- `);
1385
+ }
1383
1386
  /**
1384
1387
  * TODO: [🎺]
1385
1388
  */
@@ -1391,13 +1394,14 @@ const $isRunningInNode = new Function(`
1391
1394
  *
1392
1395
  * @public exported from `@promptbook/utils`
1393
1396
  */
1394
- const $isRunningInBrowser = new Function(`
1397
+ function $isRunningInBrowser() {
1395
1398
  try {
1396
- return this === window;
1397
- } catch (e) {
1399
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
1400
+ }
1401
+ catch (e) {
1398
1402
  return false;
1399
1403
  }
1400
- `);
1404
+ }
1401
1405
  /**
1402
1406
  * TODO: [🎺]
1403
1407
  */
@@ -1409,13 +1413,15 @@ const $isRunningInBrowser = new Function(`
1409
1413
  *
1410
1414
  * @public exported from `@promptbook/utils`
1411
1415
  */
1412
- const $isRunningInJest = new Function(`
1416
+ function $isRunningInJest() {
1417
+ var _a;
1413
1418
  try {
1414
- return process.env.JEST_WORKER_ID !== undefined;
1415
- } catch (e) {
1419
+ return typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.JEST_WORKER_ID) !== undefined;
1420
+ }
1421
+ catch (e) {
1416
1422
  return false;
1417
1423
  }
1418
- `);
1424
+ }
1419
1425
  /**
1420
1426
  * TODO: [🎺]
1421
1427
  */
@@ -1427,17 +1433,17 @@ const $isRunningInJest = new Function(`
1427
1433
  *
1428
1434
  * @public exported from `@promptbook/utils`
1429
1435
  */
1430
- const $isRunningInWebWorker = new Function(`
1436
+ function $isRunningInWebWorker() {
1431
1437
  try {
1432
- if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
1433
- return true;
1434
- } else {
1435
- return false;
1436
- }
1437
- } catch (e) {
1438
+ // Note: Check for importScripts which is specific to workers
1439
+ // and not available in the main browser thread
1440
+ return (typeof self !== 'undefined' &&
1441
+ typeof self.importScripts === 'function');
1442
+ }
1443
+ catch (e) {
1438
1444
  return false;
1439
1445
  }
1440
- `);
1446
+ }
1441
1447
  /**
1442
1448
  * TODO: [🎺]
1443
1449
  */
@@ -1955,7 +1961,7 @@ function $registeredLlmToolsMessage() {
1955
1961
  ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
1956
1962
  ${morePieces.join('; ')}
1957
1963
  `);
1958
- if ($isRunningInNode) {
1964
+ if ($isRunningInNode()) {
1959
1965
  if (isInstalled && isFullyConfigured) {
1960
1966
  providerMessage = colors.green(providerMessage);
1961
1967
  }
@@ -3717,6 +3723,7 @@ class RemoteLlmExecutionTools {
3717
3723
  }
3718
3724
  }
3719
3725
  /**
3726
+ * TODO: !!!! Deprecate pipeline server and all of its components
3720
3727
  * TODO: Maybe use `$exportJson`
3721
3728
  * TODO: [🧠][🛍] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
3722
3729
  * TODO: [🍓] Allow to list compatible models with each variant
@@ -3726,280 +3733,1166 @@ class RemoteLlmExecutionTools {
3726
3733
  */
3727
3734
 
3728
3735
  /**
3729
- * Function isValidJsonString will tell you if the string is valid JSON or not
3736
+ * Normalizes a given text to camelCase format.
3730
3737
  *
3731
- * @param value The string to check
3732
- * @returns `true` if the string is a valid JSON string, false otherwise
3738
+ * Note: [🔂] This function is idempotent.
3733
3739
  *
3740
+ * @param text The text to be normalized.
3741
+ * @param _isFirstLetterCapital Whether the first letter should be capitalized.
3742
+ * @returns The camelCase formatted string.
3743
+ * @example 'helloWorld'
3744
+ * @example 'iLovePromptbook'
3734
3745
  * @public exported from `@promptbook/utils`
3735
3746
  */
3736
- function isValidJsonString(value /* <- [👨‍⚖️] */) {
3737
- try {
3738
- JSON.parse(value);
3739
- return true;
3740
- }
3741
- catch (error) {
3742
- assertsError(error);
3743
- if (error.message.includes('Unexpected token')) {
3744
- return false;
3747
+ function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
3748
+ let charType;
3749
+ let lastCharType = null;
3750
+ let normalizedName = '';
3751
+ for (const char of text) {
3752
+ let normalizedChar;
3753
+ if (/^[a-z]$/.test(char)) {
3754
+ charType = 'LOWERCASE';
3755
+ normalizedChar = char;
3745
3756
  }
3746
- return false;
3757
+ else if (/^[A-Z]$/.test(char)) {
3758
+ charType = 'UPPERCASE';
3759
+ normalizedChar = char.toLowerCase();
3760
+ }
3761
+ else if (/^[0-9]$/.test(char)) {
3762
+ charType = 'NUMBER';
3763
+ normalizedChar = char;
3764
+ }
3765
+ else {
3766
+ charType = 'OTHER';
3767
+ normalizedChar = '';
3768
+ }
3769
+ if (!lastCharType) {
3770
+ if (_isFirstLetterCapital) {
3771
+ normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
3772
+ }
3773
+ }
3774
+ else if (charType !== lastCharType &&
3775
+ !(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
3776
+ !(lastCharType === 'NUMBER') &&
3777
+ !(charType === 'NUMBER')) {
3778
+ normalizedChar = normalizedChar.toUpperCase(); //TODO: [🌺] DRY
3779
+ }
3780
+ normalizedName += normalizedChar;
3781
+ lastCharType = charType;
3747
3782
  }
3783
+ return normalizedName;
3748
3784
  }
3749
-
3750
3785
  /**
3751
- * Makes first letter of a string uppercase
3752
- *
3753
- * Note: [🔂] This function is idempotent.
3754
- *
3755
- * @public exported from `@promptbook/utils`
3786
+ * TODO: [🌺] Use some intermediate util splitWords
3756
3787
  */
3757
- function capitalize(word) {
3758
- return word.substring(0, 1).toUpperCase() + word.substring(1);
3759
- }
3760
3788
 
3761
3789
  /**
3762
- * Extracts all code blocks from markdown.
3790
+ * Creates a Mermaid graph based on the promptbook
3763
3791
  *
3764
- * Note: There are multiple similar functions:
3765
- * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
3766
- * - `extractJsonBlock` extracts exactly one valid JSON code block
3767
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
3768
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
3792
+ * Note: The result is not wrapped in a Markdown code block
3769
3793
  *
3770
- * @param markdown any valid markdown
3771
- * @returns code blocks with language and content
3772
- * @throws {ParseError} if block is not closed properly
3773
- * @public exported from `@promptbook/markdown-utils`
3794
+ * @public exported from `@promptbook/utils`
3774
3795
  */
3775
- function extractAllBlocksFromMarkdown(markdown) {
3776
- const codeBlocks = [];
3777
- const lines = markdown.split('\n');
3778
- // Note: [0] Ensure that the last block notated by gt > will be closed
3779
- lines.push('');
3780
- let currentCodeBlock = null;
3781
- for (const line of lines) {
3782
- if (line.startsWith('> ') || line === '>') {
3783
- if (currentCodeBlock === null) {
3784
- currentCodeBlock = { blockNotation: '>', language: null, content: '' };
3785
- } /* not else */
3786
- if (currentCodeBlock.blockNotation === '>') {
3787
- if (currentCodeBlock.content !== '') {
3788
- currentCodeBlock.content += '\n';
3789
- }
3790
- currentCodeBlock.content += line.slice(2);
3791
- }
3796
+ function renderPromptbookMermaid(pipelineJson, options) {
3797
+ const { linkTask = () => null } = options || {};
3798
+ const MERMAID_PREFIX = 'pipeline_';
3799
+ const MERMAID_KNOWLEDGE_NAME = MERMAID_PREFIX + 'knowledge';
3800
+ const MERMAID_RESERVED_NAME = MERMAID_PREFIX + 'reserved';
3801
+ const MERMAID_INPUT_NAME = MERMAID_PREFIX + 'input';
3802
+ const MERMAID_OUTPUT_NAME = MERMAID_PREFIX + 'output';
3803
+ const parameterNameToTaskName = (parameterName) => {
3804
+ if (parameterName === 'knowledge') {
3805
+ return MERMAID_KNOWLEDGE_NAME;
3792
3806
  }
3793
- else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '>' /* <- Note: [0] */) {
3794
- codeBlocks.push(currentCodeBlock);
3795
- currentCodeBlock = null;
3807
+ else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
3808
+ return MERMAID_RESERVED_NAME;
3796
3809
  }
3797
- /* not else */
3798
- if (line.startsWith('```')) {
3799
- const language = line.slice(3).trim() || null;
3800
- if (currentCodeBlock === null) {
3801
- currentCodeBlock = { blockNotation: '```', language, content: '' };
3802
- }
3803
- else {
3804
- if (language !== null) {
3805
- throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed and already opening new ${language} code block`);
3806
- }
3807
- codeBlocks.push(currentCodeBlock);
3808
- currentCodeBlock = null;
3809
- }
3810
+ const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
3811
+ if (!parameter) {
3812
+ throw new UnexpectedError(`Could not find {${parameterName}}`);
3813
+ // <- TODO: This causes problems when {knowledge} and other reserved parameters are used
3810
3814
  }
3811
- else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '```') {
3812
- if (currentCodeBlock.content !== '') {
3813
- currentCodeBlock.content += '\n';
3814
- }
3815
- currentCodeBlock.content += line.split('\\`\\`\\`').join('```') /* <- TODO: Maybe make proper unescape */;
3815
+ if (parameter.isInput) {
3816
+ return MERMAID_INPUT_NAME;
3816
3817
  }
3817
- }
3818
- if (currentCodeBlock !== null) {
3819
- throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed at the end of the markdown`);
3820
- }
3821
- return codeBlocks;
3818
+ const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName);
3819
+ if (!task) {
3820
+ throw new Error(`Could not find task for {${parameterName}}`);
3821
+ }
3822
+ return MERMAID_PREFIX + (task.name || normalizeTo_camelCase('task-' + titleToName(task.title)));
3823
+ };
3824
+ const inputAndIntermediateParametersMermaid = pipelineJson.tasks
3825
+ .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
3826
+ `${parameterNameToTaskName(resultingParameterName)}("${title}")`,
3827
+ ...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`),
3828
+ ])
3829
+ .join('\n');
3830
+ const outputParametersMermaid = pipelineJson.parameters
3831
+ .filter(({ isOutput }) => isOutput)
3832
+ .map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->${MERMAID_OUTPUT_NAME}`)
3833
+ .join('\n');
3834
+ const linksMermaid = pipelineJson.tasks
3835
+ .map((task) => {
3836
+ const link = linkTask(task);
3837
+ if (link === null) {
3838
+ return '';
3839
+ }
3840
+ const { href, title } = link;
3841
+ const taskName = parameterNameToTaskName(task.resultingParameterName);
3842
+ return `click ${taskName} href "${href}" "${title}";`;
3843
+ })
3844
+ .filter((line) => line !== '')
3845
+ .join('\n');
3846
+ const interactionPointsMermaid = Object.entries({
3847
+ [MERMAID_INPUT_NAME]: 'Input',
3848
+ [MERMAID_OUTPUT_NAME]: 'Output',
3849
+ [MERMAID_RESERVED_NAME]: 'Other',
3850
+ [MERMAID_KNOWLEDGE_NAME]: 'Knowledge',
3851
+ })
3852
+ .filter(([MERMAID_NAME]) => (inputAndIntermediateParametersMermaid + outputParametersMermaid).includes(MERMAID_NAME))
3853
+ .map(([MERMAID_NAME, title]) => `${MERMAID_NAME}((${title})):::${MERMAID_NAME}`)
3854
+ .join('\n');
3855
+ const promptbookMermaid = spaceTrim$1((block) => `
3856
+
3857
+ %% 🔮 Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually
3858
+
3859
+ flowchart LR
3860
+ subgraph "${pipelineJson.title}"
3861
+
3862
+ %% Basic configuration
3863
+ direction TB
3864
+
3865
+ %% Interaction points from pipeline to outside
3866
+ ${block(interactionPointsMermaid)}
3867
+
3868
+ %% Input and intermediate parameters
3869
+ ${block(inputAndIntermediateParametersMermaid)}
3870
+
3871
+
3872
+ %% Output parameters
3873
+ ${block(outputParametersMermaid)}
3874
+
3875
+ %% Links
3876
+ ${block(linksMermaid)}
3877
+
3878
+ %% Styles
3879
+ classDef ${MERMAID_INPUT_NAME} color: grey;
3880
+ classDef ${MERMAID_OUTPUT_NAME} color: grey;
3881
+ classDef ${MERMAID_RESERVED_NAME} color: grey;
3882
+ classDef ${MERMAID_KNOWLEDGE_NAME} color: grey;
3883
+
3884
+ end;
3885
+
3886
+ `);
3887
+ return promptbookMermaid;
3822
3888
  }
3823
3889
  /**
3824
- * TODO: Maybe name for `blockNotation` instead of '```' and '>'
3890
+ * TODO: [🧠] FOREACH in mermaid graph
3891
+ * TODO: [🧠] Knowledge in mermaid graph
3892
+ * TODO: [🧠] Personas in mermaid graph
3893
+ * TODO: Maybe use some Mermaid package instead of string templating
3894
+ * TODO: [🕌] When more than 2 functionalities, split into separate functions
3825
3895
  */
3826
3896
 
3827
3897
  /**
3828
- * Extracts extracts exactly one valid JSON code block
3829
- *
3830
- * - When given string is a valid JSON as it is, it just returns it
3831
- * - When there is no JSON code block the function throws a `ParseError`
3832
- * - When there are multiple JSON code blocks the function throws a `ParseError`
3833
- *
3834
- * Note: It is not important if marked as ```json BUT if it is VALID JSON
3835
- * Note: There are multiple similar function:
3836
- * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
3837
- * - `extractJsonBlock` extracts exactly one valid JSON code block
3838
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
3839
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
3898
+ * Serializes an error into a [🚉] JSON-serializable object
3840
3899
  *
3841
- * @public exported from `@promptbook/markdown-utils`
3842
- * @throws {ParseError} if there is no valid JSON block in the markdown
3900
+ * @public exported from `@promptbook/utils`
3843
3901
  */
3844
- function extractJsonBlock(markdown) {
3845
- if (isValidJsonString(markdown)) {
3846
- return markdown;
3847
- }
3848
- const codeBlocks = extractAllBlocksFromMarkdown(markdown);
3849
- const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
3850
- if (jsonBlocks.length === 0) {
3851
- throw new Error('There is no valid JSON block in the markdown');
3902
+ function serializeError(error) {
3903
+ const { name, message, stack } = error;
3904
+ const { id } = error;
3905
+ if (!Object.keys(ALL_ERRORS).includes(name)) {
3906
+ console.error(spaceTrim$2((block) => `
3907
+
3908
+ Cannot serialize error with name "${name}"
3909
+
3910
+ Authors of Promptbook probably forgot to add this error into the list of errors:
3911
+ https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
3912
+
3913
+
3914
+ ${block(stack || message)}
3915
+
3916
+ `));
3917
+ }
3918
+ return {
3919
+ name: name,
3920
+ message,
3921
+ stack,
3922
+ id, // Include id in the serialized object
3923
+ };
3924
+ }
3925
+
3926
+ /**
3927
+ * Async version of Array.forEach
3928
+ *
3929
+ * @param array - Array to iterate over
3930
+ * @param options - Options for the function
3931
+ * @param callbackfunction - Function to call for each item
3932
+ * @public exported from `@promptbook/utils`
3933
+ * @deprecated [🪂] Use queues instead
3934
+ */
3935
+ async function forEachAsync(array, options, callbackfunction) {
3936
+ const { maxParallelCount = Infinity } = options;
3937
+ let index = 0;
3938
+ let runningTasks = [];
3939
+ const tasks = [];
3940
+ for (const item of array) {
3941
+ const currentIndex = index++;
3942
+ const task = callbackfunction(item, currentIndex, array);
3943
+ tasks.push(task);
3944
+ runningTasks.push(task);
3945
+ /* not await */ Promise.resolve(task).then(() => {
3946
+ runningTasks = runningTasks.filter((t) => t !== task);
3947
+ });
3948
+ if (maxParallelCount < runningTasks.length) {
3949
+ await Promise.race(runningTasks);
3950
+ }
3951
+ }
3952
+ await Promise.all(tasks);
3953
+ }
3954
+
3955
+ /**
3956
+ * Function to check if a string is valid CSV
3957
+ *
3958
+ * @param value The string to check
3959
+ * @returns `true` if the string is a valid CSV string, false otherwise
3960
+ *
3961
+ * @public exported from `@promptbook/utils`
3962
+ */
3963
+ function isValidCsvString(value) {
3964
+ try {
3965
+ // A simple check for CSV format: at least one comma and no invalid characters
3966
+ if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
3967
+ return true;
3968
+ }
3969
+ return false;
3970
+ }
3971
+ catch (error) {
3972
+ assertsError(error);
3973
+ return false;
3974
+ }
3975
+ }
3976
+
3977
+ /**
3978
+ * Function isValidJsonString will tell you if the string is valid JSON or not
3979
+ *
3980
+ * @param value The string to check
3981
+ * @returns `true` if the string is a valid JSON string, false otherwise
3982
+ *
3983
+ * @public exported from `@promptbook/utils`
3984
+ */
3985
+ function isValidJsonString(value /* <- [👨‍⚖️] */) {
3986
+ try {
3987
+ JSON.parse(value);
3988
+ return true;
3989
+ }
3990
+ catch (error) {
3991
+ assertsError(error);
3992
+ if (error.message.includes('Unexpected token')) {
3993
+ return false;
3994
+ }
3995
+ return false;
3996
+ }
3997
+ }
3998
+
3999
+ /**
4000
+ * Function to check if a string is valid XML
4001
+ *
4002
+ * @param value
4003
+ * @returns `true` if the string is a valid XML string, false otherwise
4004
+ *
4005
+ * @public exported from `@promptbook/utils`
4006
+ */
4007
+ function isValidXmlString(value) {
4008
+ try {
4009
+ const parser = new DOMParser();
4010
+ const parsedDocument = parser.parseFromString(value, 'application/xml');
4011
+ const parserError = parsedDocument.getElementsByTagName('parsererror');
4012
+ if (parserError.length > 0) {
4013
+ return false;
4014
+ }
4015
+ return true;
4016
+ }
4017
+ catch (error) {
4018
+ assertsError(error);
4019
+ return false;
4020
+ }
4021
+ }
4022
+
4023
+ /**
4024
+ * Format either small or big number
4025
+ *
4026
+ * @public exported from `@promptbook/utils`
4027
+ */
4028
+ function numberToString(value) {
4029
+ if (value === 0) {
4030
+ return '0';
4031
+ }
4032
+ else if (Number.isNaN(value)) {
4033
+ return VALUE_STRINGS.nan;
4034
+ }
4035
+ else if (value === Infinity) {
4036
+ return VALUE_STRINGS.infinity;
4037
+ }
4038
+ else if (value === -Infinity) {
4039
+ return VALUE_STRINGS.negativeInfinity;
4040
+ }
4041
+ for (let exponent = 0; exponent < 15; exponent++) {
4042
+ const factor = 10 ** exponent;
4043
+ const valueRounded = Math.round(value * factor) / factor;
4044
+ if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
4045
+ return valueRounded.toFixed(exponent);
4046
+ }
4047
+ }
4048
+ return value.toString();
4049
+ }
4050
+
4051
+ /**
4052
+ * Function `valueToString` will convert the given value to string
4053
+ * This is useful and used in the `templateParameters` function
4054
+ *
4055
+ * Note: This function is not just calling `toString` method
4056
+ * It's more complex and can handle this conversion specifically for LLM models
4057
+ * See `VALUE_STRINGS`
4058
+ *
4059
+ * Note: There are 2 similar functions
4060
+ * - `valueToString` converts value to string for LLM models as human-readable string
4061
+ * - `asSerializable` converts value to string to preserve full information to be able to convert it back
4062
+ *
4063
+ * @public exported from `@promptbook/utils`
4064
+ */
4065
+ function valueToString(value) {
4066
+ try {
4067
+ if (value === '') {
4068
+ return VALUE_STRINGS.empty;
4069
+ }
4070
+ else if (value === null) {
4071
+ return VALUE_STRINGS.null;
4072
+ }
4073
+ else if (value === undefined) {
4074
+ return VALUE_STRINGS.undefined;
4075
+ }
4076
+ else if (typeof value === 'string') {
4077
+ return value;
4078
+ }
4079
+ else if (typeof value === 'number') {
4080
+ return numberToString(value);
4081
+ }
4082
+ else if (value instanceof Date) {
4083
+ return value.toISOString();
4084
+ }
4085
+ else {
4086
+ try {
4087
+ return JSON.stringify(value);
4088
+ }
4089
+ catch (error) {
4090
+ if (error instanceof TypeError && error.message.includes('circular structure')) {
4091
+ return VALUE_STRINGS.circular;
4092
+ }
4093
+ throw error;
4094
+ }
4095
+ }
4096
+ }
4097
+ catch (error) {
4098
+ assertsError(error);
4099
+ console.error(error);
4100
+ return VALUE_STRINGS.unserializable;
4101
+ }
4102
+ }
4103
+
4104
+ /**
4105
+ * Replaces parameters in template with values from parameters object
4106
+ *
4107
+ * Note: This function is not places strings into string,
4108
+ * It's more complex and can handle this operation specifically for LLM models
4109
+ *
4110
+ * @param template the template with parameters in {curly} braces
4111
+ * @param parameters the object with parameters
4112
+ * @returns the template with replaced parameters
4113
+ * @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
4114
+ * @public exported from `@promptbook/utils`
4115
+ */
4116
+ function templateParameters(template, parameters) {
4117
+ for (const [parameterName, parameterValue] of Object.entries(parameters)) {
4118
+ if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
4119
+ throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
4120
+ }
4121
+ else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
4122
+ // TODO: [🍵]
4123
+ throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
4124
+ }
4125
+ }
4126
+ let replacedTemplates = template;
4127
+ let match;
4128
+ let loopLimit = LOOP_LIMIT;
4129
+ while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
4130
+ .exec(replacedTemplates))) {
4131
+ if (loopLimit-- < 0) {
4132
+ throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
4133
+ }
4134
+ const precol = match.groups.precol;
4135
+ const parameterName = match.groups.parameterName;
4136
+ if (parameterName === '') {
4137
+ // Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
4138
+ continue;
4139
+ }
4140
+ if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
4141
+ throw new PipelineExecutionError('Parameter is already opened or not closed');
4142
+ }
4143
+ if (parameters[parameterName] === undefined) {
4144
+ throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
4145
+ }
4146
+ let parameterValue = parameters[parameterName];
4147
+ if (parameterValue === undefined) {
4148
+ throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
4149
+ }
4150
+ parameterValue = valueToString(parameterValue);
4151
+ // Escape curly braces in parameter values to prevent prompt-injection
4152
+ parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
4153
+ if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
4154
+ parameterValue = parameterValue
4155
+ .split('\n')
4156
+ .map((line, index) => (index === 0 ? line : `${precol}${line}`))
4157
+ .join('\n');
4158
+ }
4159
+ replacedTemplates =
4160
+ replacedTemplates.substring(0, match.index + precol.length) +
4161
+ parameterValue +
4162
+ replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
4163
+ }
4164
+ // [💫] Check if there are parameters that are not closed properly
4165
+ if (/{\w+$/.test(replacedTemplates)) {
4166
+ throw new PipelineExecutionError('Parameter is not closed');
4167
+ }
4168
+ // [💫] Check if there are parameters that are not opened properly
4169
+ if (/^\w+}/.test(replacedTemplates)) {
4170
+ throw new PipelineExecutionError('Parameter is not opened');
4171
+ }
4172
+ return replacedTemplates;
4173
+ }
4174
+
4175
+ /**
4176
+ * Number of characters per standard line with 11pt Arial font size.
4177
+ *
4178
+ * @public exported from `@promptbook/utils`
4179
+ */
4180
+ const CHARACTERS_PER_STANDARD_LINE = 63;
4181
+ /**
4182
+ * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
4183
+ *
4184
+ * @public exported from `@promptbook/utils`
4185
+ */
4186
+ const LINES_PER_STANDARD_PAGE = 44;
4187
+ /**
4188
+ * TODO: [🧠] Should be this `constants.ts` or `config.ts`?
4189
+ * Note: [💞] Ignore a discrepancy between file name and entity name
4190
+ */
4191
+
4192
+ /**
4193
+ * Counts number of characters in the text
4194
+ *
4195
+ * @public exported from `@promptbook/utils`
4196
+ */
4197
+ function countCharacters(text) {
4198
+ // Remove null characters
4199
+ text = text.replace(/\0/g, '');
4200
+ // Replace emojis (and also ZWJ sequence) with hyphens
4201
+ text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
4202
+ text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
4203
+ text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
4204
+ return text.length;
4205
+ }
4206
+ /**
4207
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4208
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4209
+ */
4210
+
4211
+ /**
4212
+ * Counts number of lines in the text
4213
+ *
4214
+ * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
4215
+ *
4216
+ * @public exported from `@promptbook/utils`
4217
+ */
4218
+ function countLines(text) {
4219
+ if (text === '') {
4220
+ return 0;
4221
+ }
4222
+ text = text.replace('\r\n', '\n');
4223
+ text = text.replace('\r', '\n');
4224
+ const lines = text.split('\n');
4225
+ return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
4226
+ }
4227
+ /**
4228
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4229
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4230
+ */
4231
+
4232
+ /**
4233
+ * Counts number of pages in the text
4234
+ *
4235
+ * 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.
4236
+ *
4237
+ * @public exported from `@promptbook/utils`
4238
+ */
4239
+ function countPages(text) {
4240
+ return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
4241
+ }
4242
+ /**
4243
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4244
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4245
+ */
4246
+
4247
+ /**
4248
+ * Counts number of paragraphs in the text
4249
+ *
4250
+ * @public exported from `@promptbook/utils`
4251
+ */
4252
+ function countParagraphs(text) {
4253
+ return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
4254
+ }
4255
+ /**
4256
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4257
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4258
+ */
4259
+
4260
+ /**
4261
+ * Split text into sentences
4262
+ *
4263
+ * @public exported from `@promptbook/utils`
4264
+ */
4265
+ function splitIntoSentences(text) {
4266
+ return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
4267
+ }
4268
+ /**
4269
+ * Counts number of sentences in the text
4270
+ *
4271
+ * @public exported from `@promptbook/utils`
4272
+ */
4273
+ function countSentences(text) {
4274
+ return splitIntoSentences(text).length;
4275
+ }
4276
+ /**
4277
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4278
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4279
+ */
4280
+
4281
+ /**
4282
+ * Counts number of words in the text
4283
+ *
4284
+ * @public exported from `@promptbook/utils`
4285
+ */
4286
+ function countWords(text) {
4287
+ text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
4288
+ text = removeDiacritics(text);
4289
+ // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
4290
+ text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
4291
+ return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
4292
+ }
4293
+ /**
4294
+ * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
4295
+ * TODO: [🧠][✌️] Make some Promptbook-native token system
4296
+ * TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
4297
+ */
4298
+
4299
+ /**
4300
+ * Index of all counter functions
4301
+ *
4302
+ * @public exported from `@promptbook/utils`
4303
+ */
4304
+ const CountUtils = {
4305
+ CHARACTERS: countCharacters,
4306
+ WORDS: countWords,
4307
+ SENTENCES: countSentences,
4308
+ PARAGRAPHS: countParagraphs,
4309
+ LINES: countLines,
4310
+ PAGES: countPages,
4311
+ };
4312
+ /**
4313
+ * TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
4314
+ * Note: [💞] Ignore a discrepancy between file name and entity name
4315
+ */
4316
+
4317
+ /**
4318
+ * Simple wrapper `new Date().toISOString()`
4319
+ *
4320
+ * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
4321
+ *
4322
+ * @returns string_date branded type
4323
+ * @public exported from `@promptbook/utils`
4324
+ */
4325
+ function $getCurrentDate() {
4326
+ return new Date().toISOString();
4327
+ }
4328
+
4329
+ /**
4330
+ * Computes SHA-256 hash of the given object
4331
+ *
4332
+ * @public exported from `@promptbook/utils`
4333
+ */
4334
+ function computeHash(value) {
4335
+ return SHA256(hexEncoder.parse(spaceTrim$2(valueToString(value)))).toString( /* hex */);
4336
+ }
4337
+ /**
4338
+ * TODO: [🥬][🥬] Use this ACRY
4339
+ */
4340
+
4341
+ /**
4342
+ * Function parseNumber will parse number from string
4343
+ *
4344
+ * Note: [🔂] This function is idempotent.
4345
+ * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
4346
+ * Note: it also works only with decimal numbers
4347
+ *
4348
+ * @returns parsed number
4349
+ * @throws {ParseError} if the value is not a number
4350
+ *
4351
+ * @public exported from `@promptbook/utils`
4352
+ */
4353
+ function parseNumber(value) {
4354
+ const originalValue = value;
4355
+ if (typeof value === 'number') {
4356
+ value = value.toString(); // <- TODO: Maybe more efficient way to do this
4357
+ }
4358
+ if (typeof value !== 'string') {
4359
+ return 0;
4360
+ }
4361
+ value = value.trim();
4362
+ if (value.startsWith('+')) {
4363
+ return parseNumber(value.substring(1));
4364
+ }
4365
+ if (value.startsWith('-')) {
4366
+ const number = parseNumber(value.substring(1));
4367
+ if (number === 0) {
4368
+ return 0; // <- Note: To prevent -0
4369
+ }
4370
+ return -number;
4371
+ }
4372
+ value = value.replace(/,/g, '.');
4373
+ value = value.toUpperCase();
4374
+ if (value === '') {
4375
+ return 0;
4376
+ }
4377
+ if (value === '♾' || value.startsWith('INF')) {
4378
+ return Infinity;
4379
+ }
4380
+ if (value.includes('/')) {
4381
+ const [numerator_, denominator_] = value.split('/');
4382
+ const numerator = parseNumber(numerator_);
4383
+ const denominator = parseNumber(denominator_);
4384
+ if (denominator === 0) {
4385
+ throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
4386
+ }
4387
+ return numerator / denominator;
4388
+ }
4389
+ if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
4390
+ return 0;
4391
+ }
4392
+ if (value.includes('E')) {
4393
+ const [significand, exponent] = value.split('E');
4394
+ return parseNumber(significand) * 10 ** parseNumber(exponent);
4395
+ }
4396
+ if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
4397
+ throw new ParseError(`Unable to parse number from "${originalValue}"`);
4398
+ }
4399
+ const num = parseFloat(value);
4400
+ if (isNaN(num)) {
4401
+ throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
4402
+ }
4403
+ return num;
4404
+ }
4405
+ /**
4406
+ * TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
4407
+ * TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
4408
+ */
4409
+
4410
+ /**
4411
+ * Makes first letter of a string uppercase
4412
+ *
4413
+ * Note: [🔂] This function is idempotent.
4414
+ *
4415
+ * @public exported from `@promptbook/utils`
4416
+ */
4417
+ function capitalize(word) {
4418
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
4419
+ }
4420
+
4421
+ /**
4422
+ * Makes first letter of a string lowercase
4423
+ *
4424
+ * Note: [🔂] This function is idempotent.
4425
+ *
4426
+ * @public exported from `@promptbook/utils`
4427
+ */
4428
+ function decapitalize(word) {
4429
+ return word.substring(0, 1).toLowerCase() + word.substring(1);
4430
+ }
4431
+
4432
+ /**
4433
+ * Parses keywords from a string
4434
+ *
4435
+ * @param {string} input
4436
+ * @returns {Set} of keywords without diacritics in lowercase
4437
+ * @public exported from `@promptbook/utils`
4438
+ */
4439
+ function parseKeywordsFromString(input) {
4440
+ const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
4441
+ .toLowerCase()
4442
+ .split(/[^a-z0-9]+/gs)
4443
+ .filter((value) => value);
4444
+ return new Set(keywords);
4445
+ }
4446
+
4447
+ /**
4448
+ * Converts a name string into a URI-compatible format.
4449
+ *
4450
+ * @param name The string to be converted to a URI-compatible format.
4451
+ * @returns A URI-compatible string derived from the input name.
4452
+ * @example 'Hello World' -> 'hello-world'
4453
+ * @public exported from `@promptbook/utils`
4454
+ */
4455
+ function nameToUriPart(name) {
4456
+ let uriPart = name;
4457
+ uriPart = uriPart.toLowerCase();
4458
+ uriPart = removeDiacritics(uriPart);
4459
+ uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
4460
+ uriPart = uriPart.replace(/^-+/, '');
4461
+ uriPart = uriPart.replace(/-+$/, '');
4462
+ return uriPart;
4463
+ }
4464
+
4465
+ /**
4466
+ * Converts a given name into URI-compatible parts.
4467
+ *
4468
+ * @param name The name to be converted into URI parts.
4469
+ * @returns An array of URI-compatible parts derived from the name.
4470
+ * @example 'Example Name' -> ['example', 'name']
4471
+ * @public exported from `@promptbook/utils`
4472
+ */
4473
+ function nameToUriParts(name) {
4474
+ return nameToUriPart(name)
4475
+ .split('-')
4476
+ .filter((value) => value !== '');
4477
+ }
4478
+
4479
+ /**
4480
+ * Normalizes a given text to PascalCase format.
4481
+ *
4482
+ * Note: [🔂] This function is idempotent.
4483
+ *
4484
+ * @param text @public exported from `@promptbook/utils`
4485
+ * @returns
4486
+ * @example 'HelloWorld'
4487
+ * @example 'ILovePromptbook'
4488
+ * @public exported from `@promptbook/utils`
4489
+ */
4490
+ function normalizeTo_PascalCase(text) {
4491
+ return normalizeTo_camelCase(text, true);
4492
+ }
4493
+
4494
+ /**
4495
+ * Take every whitespace (space, new line, tab) and replace it with a single space
4496
+ *
4497
+ * Note: [🔂] This function is idempotent.
4498
+ *
4499
+ * @public exported from `@promptbook/utils`
4500
+ */
4501
+ function normalizeWhitespaces(sentence) {
4502
+ return sentence.replace(/\s+/gs, ' ').trim();
4503
+ }
4504
+
4505
+ /**
4506
+ * Removes quotes from a string
4507
+ *
4508
+ * Note: [🔂] This function is idempotent.
4509
+ * Tip: This is very useful for post-processing of the result of the LLM model
4510
+ * Note: This function removes only the same quotes from the beginning and the end of the string
4511
+ * Note: There are two similar functions:
4512
+ * - `removeQuotes` which removes only bounding quotes
4513
+ * - `unwrapResult` which removes whole introduce sentence
4514
+ *
4515
+ * @param text optionally quoted text
4516
+ * @returns text without quotes
4517
+ * @public exported from `@promptbook/utils`
4518
+ */
4519
+ function removeQuotes(text) {
4520
+ if (text.startsWith('"') && text.endsWith('"')) {
4521
+ return text.slice(1, -1);
3852
4522
  }
3853
- if (jsonBlocks.length > 1) {
3854
- throw new Error('There are multiple JSON code blocks in the markdown');
4523
+ if (text.startsWith("'") && text.endsWith("'")) {
4524
+ return text.slice(1, -1);
3855
4525
  }
3856
- return jsonBlocks[0].content;
4526
+ return text;
3857
4527
  }
4528
+
3858
4529
  /**
3859
- * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
3860
- * TODO: [🏢] Make this logic part of `JsonFormatParser` or `isValidJsonString`
4530
+ * Adds suffix to the URL
4531
+ *
4532
+ * @public exported from `@promptbook/utils`
3861
4533
  */
4534
+ function suffixUrl(value, suffix) {
4535
+ const baseUrl = value.href.endsWith('/') ? value.href.slice(0, -1) : value.href;
4536
+ const normalizedSuffix = suffix.replace(/\/+/g, '/');
4537
+ return (baseUrl + normalizedSuffix);
4538
+ }
3862
4539
 
3863
4540
  /**
3864
- * Counts number of characters in the text
4541
+ * Removes quotes and optional introduce text from a string
4542
+ *
4543
+ * Tip: This is very useful for post-processing of the result of the LLM model
4544
+ * Note: This function trims the text and removes whole introduce sentence if it is present
4545
+ * Note: There are two similar functions:
4546
+ * - `removeQuotes` which removes only bounding quotes
4547
+ * - `unwrapResult` which removes whole introduce sentence
3865
4548
  *
4549
+ * @param text optionally quoted text
4550
+ * @returns text without quotes
3866
4551
  * @public exported from `@promptbook/utils`
3867
4552
  */
3868
- function countCharacters(text) {
3869
- // Remove null characters
3870
- text = text.replace(/\0/g, '');
3871
- // Replace emojis (and also ZWJ sequence) with hyphens
3872
- text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
3873
- text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
3874
- text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
3875
- return text.length;
4553
+ function unwrapResult(text, options) {
4554
+ const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
4555
+ let trimmedText = text;
4556
+ // Remove leading and trailing spaces and newlines
4557
+ if (isTrimmed) {
4558
+ trimmedText = spaceTrim$1(trimmedText);
4559
+ }
4560
+ let processedText = trimmedText;
4561
+ if (isIntroduceSentenceRemoved) {
4562
+ const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
4563
+ if (introduceSentenceRegex.test(text)) {
4564
+ // Remove the introduce sentence and quotes by replacing it with an empty string
4565
+ processedText = processedText.replace(introduceSentenceRegex, '');
4566
+ }
4567
+ processedText = spaceTrim$1(processedText);
4568
+ }
4569
+ if (processedText.length < 3) {
4570
+ return trimmedText;
4571
+ }
4572
+ if (processedText.includes('\n')) {
4573
+ return trimmedText;
4574
+ }
4575
+ // Remove the quotes by extracting the substring without the first and last characters
4576
+ const unquotedText = processedText.slice(1, -1);
4577
+ // Check if the text starts and ends with quotes
4578
+ if ([
4579
+ ['"', '"'],
4580
+ ["'", "'"],
4581
+ ['`', '`'],
4582
+ ['*', '*'],
4583
+ ['_', '_'],
4584
+ ['„', '“'],
4585
+ ['«', '»'] /* <- QUOTES to config */,
4586
+ ].some(([startQuote, endQuote]) => {
4587
+ if (!processedText.startsWith(startQuote)) {
4588
+ return false;
4589
+ }
4590
+ if (!processedText.endsWith(endQuote)) {
4591
+ return false;
4592
+ }
4593
+ if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
4594
+ return false;
4595
+ }
4596
+ if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
4597
+ return false;
4598
+ }
4599
+ return true;
4600
+ })) {
4601
+ return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
4602
+ }
4603
+ else {
4604
+ return processedText;
4605
+ }
3876
4606
  }
3877
4607
  /**
3878
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3879
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4608
+ * TODO: [🧠] Should this also unwrap the (parenthesis)
3880
4609
  */
3881
4610
 
3882
4611
  /**
3883
- * Number of characters per standard line with 11pt Arial font size.
4612
+ * Parses the task and returns the list of all parameter names
3884
4613
  *
4614
+ * @param template the string template with parameters in {curly} braces
4615
+ * @returns the list of parameter names
3885
4616
  * @public exported from `@promptbook/utils`
3886
4617
  */
3887
- const CHARACTERS_PER_STANDARD_LINE = 63;
4618
+ function extractParameterNames(template) {
4619
+ const matches = template.matchAll(/{\w+}/g);
4620
+ const parameterNames = new Set();
4621
+ for (const match of matches) {
4622
+ const parameterName = match[0].slice(1, -1);
4623
+ parameterNames.add(parameterName);
4624
+ }
4625
+ return parameterNames;
4626
+ }
4627
+
3888
4628
  /**
3889
- * Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
3890
- *
4629
+ * Recursively converts JSON strings to JSON objects
4630
+
3891
4631
  * @public exported from `@promptbook/utils`
3892
4632
  */
3893
- const LINES_PER_STANDARD_PAGE = 44;
4633
+ function jsonStringsToJsons(object) {
4634
+ if (object === null) {
4635
+ return object;
4636
+ }
4637
+ if (Array.isArray(object)) {
4638
+ return object.map(jsonStringsToJsons);
4639
+ }
4640
+ if (typeof object !== 'object') {
4641
+ return object;
4642
+ }
4643
+ const newObject = { ...object };
4644
+ for (const [key, value] of Object.entries(object)) {
4645
+ if (typeof value === 'string' && isValidJsonString(value)) {
4646
+ newObject[key] = jsonParse(value);
4647
+ }
4648
+ else {
4649
+ newObject[key] = jsonStringsToJsons(value);
4650
+ }
4651
+ }
4652
+ return newObject;
4653
+ }
3894
4654
  /**
3895
- * TODO: [🧠] Should be this `constants.ts` or `config.ts`?
3896
- * Note: [💞] Ignore a discrepancy between file name and entity name
4655
+ * TODO: Type the return type correctly
3897
4656
  */
3898
4657
 
3899
4658
  /**
3900
- * Counts number of lines in the text
3901
- *
3902
- * Note: This does not check only for the presence of newlines, but also for the length of the standard line.
4659
+ * Create difference set of two sets.
3903
4660
  *
4661
+ * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
3904
4662
  * @public exported from `@promptbook/utils`
3905
4663
  */
3906
- function countLines(text) {
3907
- if (text === '') {
3908
- return 0;
4664
+ function difference(a, b, isEqual = (a, b) => a === b) {
4665
+ const diff = new Set();
4666
+ for (const itemA of Array.from(a)) {
4667
+ if (!Array.from(b).some((itemB) => isEqual(itemA, itemB))) {
4668
+ diff.add(itemA);
4669
+ }
3909
4670
  }
3910
- text = text.replace('\r\n', '\n');
3911
- text = text.replace('\r', '\n');
3912
- const lines = text.split('\n');
3913
- return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
4671
+ return diff;
3914
4672
  }
3915
4673
  /**
3916
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3917
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4674
+ * TODO: [🧠][💯] Maybe also implement symmetricDifference
3918
4675
  */
3919
4676
 
3920
4677
  /**
3921
- * Counts number of pages in the text
3922
- *
3923
- * 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.
4678
+ * Creates a new set with all elements that are present in either set
3924
4679
  *
4680
+ * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
3925
4681
  * @public exported from `@promptbook/utils`
3926
4682
  */
3927
- function countPages(text) {
3928
- return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
4683
+ function union(...sets) {
4684
+ const union = new Set();
4685
+ for (const set of sets) {
4686
+ for (const item of Array.from(set)) {
4687
+ union.add(item);
4688
+ }
4689
+ }
4690
+ return union;
3929
4691
  }
4692
+
3930
4693
  /**
3931
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3932
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4694
+ * Checks if value is valid email
4695
+ *
4696
+ * @public exported from `@promptbook/utils`
3933
4697
  */
4698
+ function isValidEmail(email) {
4699
+ if (typeof email !== 'string') {
4700
+ return false;
4701
+ }
4702
+ if (email.split('\n').length > 1) {
4703
+ return false;
4704
+ }
4705
+ return /^.+@.+\..+$/.test(email);
4706
+ }
3934
4707
 
3935
4708
  /**
3936
- * Counts number of paragraphs in the text
4709
+ * Checks if the given value is a valid JavaScript identifier name.
3937
4710
  *
4711
+ * @param javascriptName The value to check for JavaScript identifier validity.
4712
+ * @returns `true` if the value is a valid JavaScript name, false otherwise.
3938
4713
  * @public exported from `@promptbook/utils`
3939
4714
  */
3940
- function countParagraphs(text) {
3941
- return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
4715
+ function isValidJavascriptName(javascriptName) {
4716
+ if (typeof javascriptName !== 'string') {
4717
+ return false;
4718
+ }
4719
+ return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
3942
4720
  }
4721
+
3943
4722
  /**
3944
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3945
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4723
+ * Tests if given string is valid semantic version
4724
+ *
4725
+ * Note: There are two similar functions:
4726
+ * - `isValidSemanticVersion` which tests any semantic version
4727
+ * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
4728
+ *
4729
+ * @public exported from `@promptbook/utils`
3946
4730
  */
4731
+ function isValidSemanticVersion(version) {
4732
+ if (typeof version !== 'string') {
4733
+ return false;
4734
+ }
4735
+ if (version.startsWith('0.0.0')) {
4736
+ return false;
4737
+ }
4738
+ return /^\d+\.\d+\.\d+(-\d+)?$/i.test(version);
4739
+ }
3947
4740
 
3948
4741
  /**
3949
- * Split text into sentences
4742
+ * Tests if given string is valid promptbook version
4743
+ * It looks into list of known promptbook versions.
4744
+ *
4745
+ * @see https://www.npmjs.com/package/promptbook?activeTab=versions
4746
+ * 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.
4747
+ * Note: There are two similar functions:
4748
+ * - `isValidSemanticVersion` which tests any semantic version
4749
+ * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
3950
4750
  *
3951
4751
  * @public exported from `@promptbook/utils`
3952
4752
  */
3953
- function splitIntoSentences(text) {
3954
- return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
4753
+ function isValidPromptbookVersion(version) {
4754
+ if (!isValidSemanticVersion(version)) {
4755
+ return false;
4756
+ }
4757
+ if ( /* version === '1.0.0' || */version === '2.0.0' || version === '3.0.0') {
4758
+ return false;
4759
+ }
4760
+ // <- TODO: [main] !!3 Check isValidPromptbookVersion against PROMPTBOOK_ENGINE_VERSIONS
4761
+ return true;
3955
4762
  }
4763
+
3956
4764
  /**
3957
- * Counts number of sentences in the text
4765
+ * Tests if given string is valid pipeline URL URL.
4766
+ *
4767
+ * Note: There are two similar functions:
4768
+ * - `isValidUrl` which tests any URL
4769
+ * - `isValidPipelineUrl` *(this one)* which tests just pipeline URL
3958
4770
  *
3959
4771
  * @public exported from `@promptbook/utils`
3960
4772
  */
3961
- function countSentences(text) {
3962
- return splitIntoSentences(text).length;
4773
+ function isValidPipelineUrl(url) {
4774
+ if (!isValidUrl(url)) {
4775
+ return false;
4776
+ }
4777
+ if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
4778
+ return false;
4779
+ }
4780
+ if (url.includes('#')) {
4781
+ // TODO: [🐠]
4782
+ return false;
4783
+ }
4784
+ /*
4785
+ Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
4786
+ if (isUrlOnPrivateNetwork(url)) {
4787
+ return false;
4788
+ }
4789
+ */
4790
+ return true;
3963
4791
  }
3964
4792
  /**
3965
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3966
- * TODO: [🧠][✌️] Make some Promptbook-native token system
4793
+ * TODO: [🐠] Maybe more info why the URL is invalid
3967
4794
  */
3968
4795
 
3969
4796
  /**
3970
- * Counts number of words in the text
4797
+ * Extracts all code blocks from markdown.
3971
4798
  *
3972
- * @public exported from `@promptbook/utils`
4799
+ * Note: There are multiple similar functions:
4800
+ * - `extractBlock` just extracts the content of the code block which is also used as built-in function for postprocessing
4801
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
4802
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
4803
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
4804
+ *
4805
+ * @param markdown any valid markdown
4806
+ * @returns code blocks with language and content
4807
+ * @throws {ParseError} if block is not closed properly
4808
+ * @public exported from `@promptbook/markdown-utils`
3973
4809
  */
3974
- function countWords(text) {
3975
- text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
3976
- text = removeDiacritics(text);
3977
- // Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
3978
- text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
3979
- return text.split(/[^a-zа-я0-9]+/i).filter((word) => word.length > 0).length;
4810
+ function extractAllBlocksFromMarkdown(markdown) {
4811
+ const codeBlocks = [];
4812
+ const lines = markdown.split('\n');
4813
+ // Note: [0] Ensure that the last block notated by gt > will be closed
4814
+ lines.push('');
4815
+ let currentCodeBlock = null;
4816
+ for (const line of lines) {
4817
+ if (line.startsWith('> ') || line === '>') {
4818
+ if (currentCodeBlock === null) {
4819
+ currentCodeBlock = { blockNotation: '>', language: null, content: '' };
4820
+ } /* not else */
4821
+ if (currentCodeBlock.blockNotation === '>') {
4822
+ if (currentCodeBlock.content !== '') {
4823
+ currentCodeBlock.content += '\n';
4824
+ }
4825
+ currentCodeBlock.content += line.slice(2);
4826
+ }
4827
+ }
4828
+ else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '>' /* <- Note: [0] */) {
4829
+ codeBlocks.push(currentCodeBlock);
4830
+ currentCodeBlock = null;
4831
+ }
4832
+ /* not else */
4833
+ if (line.startsWith('```')) {
4834
+ const language = line.slice(3).trim() || null;
4835
+ if (currentCodeBlock === null) {
4836
+ currentCodeBlock = { blockNotation: '```', language, content: '' };
4837
+ }
4838
+ else {
4839
+ if (language !== null) {
4840
+ throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed and already opening new ${language} code block`);
4841
+ }
4842
+ codeBlocks.push(currentCodeBlock);
4843
+ currentCodeBlock = null;
4844
+ }
4845
+ }
4846
+ else if (currentCodeBlock !== null && currentCodeBlock.blockNotation === '```') {
4847
+ if (currentCodeBlock.content !== '') {
4848
+ currentCodeBlock.content += '\n';
4849
+ }
4850
+ currentCodeBlock.content += line.split('\\`\\`\\`').join('```') /* <- TODO: Maybe make proper unescape */;
4851
+ }
4852
+ }
4853
+ if (currentCodeBlock !== null) {
4854
+ throw new ParseError(`${capitalize(currentCodeBlock.language || 'the')} code block was not closed at the end of the markdown`);
4855
+ }
4856
+ return codeBlocks;
3980
4857
  }
3981
4858
  /**
3982
- * TODO: [🥴] Implement counting in formats - like JSON, CSV, XML,...
3983
- * TODO: [🧠][✌️] Make some Promptbook-native token system
3984
- * TODO: [✌️] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
4859
+ * TODO: Maybe name for `blockNotation` instead of '```' and '>'
3985
4860
  */
3986
4861
 
3987
4862
  /**
3988
- * Index of all counter functions
4863
+ * Extracts extracts exactly one valid JSON code block
3989
4864
  *
3990
- * @public exported from `@promptbook/utils`
4865
+ * - When given string is a valid JSON as it is, it just returns it
4866
+ * - When there is no JSON code block the function throws a `ParseError`
4867
+ * - When there are multiple JSON code blocks the function throws a `ParseError`
4868
+ *
4869
+ * Note: It is not important if marked as ```json BUT if it is VALID JSON
4870
+ * Note: There are multiple similar function:
4871
+ * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
4872
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
4873
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
4874
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
4875
+ *
4876
+ * @public exported from `@promptbook/markdown-utils`
4877
+ * @throws {ParseError} if there is no valid JSON block in the markdown
3991
4878
  */
3992
- const CountUtils = {
3993
- CHARACTERS: countCharacters,
3994
- WORDS: countWords,
3995
- SENTENCES: countSentences,
3996
- PARAGRAPHS: countParagraphs,
3997
- LINES: countLines,
3998
- PAGES: countPages,
3999
- };
4879
+ function extractJsonBlock(markdown) {
4880
+ if (isValidJsonString(markdown)) {
4881
+ return markdown;
4882
+ }
4883
+ const codeBlocks = extractAllBlocksFromMarkdown(markdown);
4884
+ const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
4885
+ if (jsonBlocks.length === 0) {
4886
+ throw new Error('There is no valid JSON block in the markdown');
4887
+ }
4888
+ if (jsonBlocks.length > 1) {
4889
+ throw new Error('There are multiple JSON code blocks in the markdown');
4890
+ }
4891
+ return jsonBlocks[0].content;
4892
+ }
4000
4893
  /**
4001
- * TODO: [🧠][🤠] This should be probably as part of `TextFormatParser`
4002
- * Note: [💞] Ignore a discrepancy between file name and entity name
4894
+ * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
4895
+ * TODO: [🏢] Make this logic part of `JsonFormatParser` or `isValidJsonString`
4003
4896
  */
4004
4897
 
4005
4898
  /**
@@ -4141,35 +5034,6 @@ class MemoryStorage {
4141
5034
  }
4142
5035
  }
4143
5036
 
4144
- /**
4145
- * Simple wrapper `new Date().toISOString()`
4146
- *
4147
- * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
4148
- *
4149
- * @returns string_date branded type
4150
- * @public exported from `@promptbook/utils`
4151
- */
4152
- function $getCurrentDate() {
4153
- return new Date().toISOString();
4154
- }
4155
-
4156
- /**
4157
- * Parses the task and returns the list of all parameter names
4158
- *
4159
- * @param template the string template with parameters in {curly} braces
4160
- * @returns the list of parameter names
4161
- * @public exported from `@promptbook/utils`
4162
- */
4163
- function extractParameterNames(template) {
4164
- const matches = template.matchAll(/{\w+}/g);
4165
- const parameterNames = new Set();
4166
- for (const match of matches) {
4167
- const parameterName = match[0].slice(1, -1);
4168
- parameterNames.add(parameterName);
4169
- }
4170
- return parameterNames;
4171
- }
4172
-
4173
5037
  /**
4174
5038
  * Intercepts LLM tools and counts total usage of the tools
4175
5039
  *
@@ -4245,6 +5109,9 @@ function cacheLlmTools(llmTools, options = {}) {
4245
5109
  case 'EMBEDDING':
4246
5110
  promptResult = await llmTools.callEmbeddingModel(prompt);
4247
5111
  break variant;
5112
+ case 'IMAGE_GENERATION':
5113
+ promptResult = await llmTools.callImageGenerationModel(prompt);
5114
+ break variant;
4248
5115
  // <- case [🤖]:
4249
5116
  default:
4250
5117
  throw new PipelineExecutionError(`Unknown model variant "${prompt.modelRequirements.modelVariant}"`);
@@ -4281,12 +5148,13 @@ function cacheLlmTools(llmTools, options = {}) {
4281
5148
  }
4282
5149
  }
4283
5150
  catch (error) {
5151
+ assertsError(error);
4284
5152
  // If validation throws an unexpected error, don't cache
4285
5153
  shouldCache = false;
4286
5154
  if (isVerbose) {
4287
5155
  console.info('Not caching result due to validation error for key:', key, {
4288
5156
  content: promptResult.content,
4289
- validationError: error instanceof Error ? error.message : String(error),
5157
+ validationError: serializeError(error),
4290
5158
  });
4291
5159
  }
4292
5160
  }
@@ -4332,6 +5200,11 @@ function cacheLlmTools(llmTools, options = {}) {
4332
5200
  return /* not await */ callCommonModel(prompt);
4333
5201
  };
4334
5202
  }
5203
+ if (llmTools.callImageGenerationModel !== undefined) {
5204
+ proxyTools.callImageGenerationModel = async (prompt) => {
5205
+ return /* not await */ callCommonModel(prompt);
5206
+ };
5207
+ }
4335
5208
  // <- Note: [🤖]
4336
5209
  return proxyTools;
4337
5210
  }
@@ -4520,6 +5393,15 @@ function countUsage(llmTools) {
4520
5393
  return promptResult;
4521
5394
  };
4522
5395
  }
5396
+ if (llmTools.callImageGenerationModel !== undefined) {
5397
+ proxyTools.callImageGenerationModel = async (prompt) => {
5398
+ // console.info('[🚕] callImageGenerationModel through countTotalUsage');
5399
+ const promptResult = await llmTools.callImageGenerationModel(prompt);
5400
+ totalUsage = addUsage(totalUsage, promptResult.usage);
5401
+ spending.next(promptResult.usage);
5402
+ return promptResult;
5403
+ };
5404
+ }
4523
5405
  // <- Note: [🤖]
4524
5406
  return proxyTools;
4525
5407
  }
@@ -4528,7 +5410,7 @@ function countUsage(llmTools) {
4528
5410
  * TODO: [🧠] Is there some meaningfull way how to test this util
4529
5411
  * TODO: [🧠][🌯] Maybe a way how to hide ability to `get totalUsage`
4530
5412
  * > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
4531
- * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
5413
+ * TODO: [👷‍♂️] Write a comprehensive manual explaining the construction and usage of LLM tools in the Promptbook ecosystem
4532
5414
  */
4533
5415
 
4534
5416
  /**
@@ -4642,6 +5524,12 @@ class MultipleLlmExecutionTools {
4642
5524
  callEmbeddingModel(prompt) {
4643
5525
  return this.callCommonModel(prompt);
4644
5526
  }
5527
+ /**
5528
+ * Calls the best available embedding model
5529
+ */
5530
+ callImageGenerationModel(prompt) {
5531
+ return this.callCommonModel(prompt);
5532
+ }
4645
5533
  // <- Note: [🤖]
4646
5534
  /**
4647
5535
  * Calls the best available model
@@ -4668,6 +5556,11 @@ class MultipleLlmExecutionTools {
4668
5556
  continue llm;
4669
5557
  }
4670
5558
  return await llmExecutionTools.callEmbeddingModel(prompt);
5559
+ case 'IMAGE_GENERATION':
5560
+ if (llmExecutionTools.callImageGenerationModel === undefined) {
5561
+ continue llm;
5562
+ }
5563
+ return await llmExecutionTools.callImageGenerationModel(prompt);
4671
5564
  // <- case [🤖]:
4672
5565
  default:
4673
5566
  throw new UnexpectedError(`Unknown model variant "${prompt.modelRequirements.modelVariant}" in ${llmExecutionTools.title}`);
@@ -4992,21 +5885,6 @@ const promptbookFetch = async (urlOrRequest, init) => {
4992
5885
  * TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
4993
5886
  */
4994
5887
 
4995
- /**
4996
- * Checks if value is valid email
4997
- *
4998
- * @public exported from `@promptbook/utils`
4999
- */
5000
- function isValidEmail(email) {
5001
- if (typeof email !== 'string') {
5002
- return false;
5003
- }
5004
- if (email.split('\n').length > 1) {
5005
- return false;
5006
- }
5007
- return /^.+@.+\..+$/.test(email);
5008
- }
5009
-
5010
5888
  /**
5011
5889
  * @private utility of CLI
5012
5890
  */
@@ -5685,113 +6563,39 @@ function $initializeListScrapersCommand(program) {
5685
6563
  }));
5686
6564
  }
5687
6565
  /**
5688
- * Note: [💞] Ignore a discrepancy between file name and entity name
5689
- * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5690
- */
5691
-
5692
- /**
5693
- * Initializes `login` command for Promptbook CLI utilities
5694
- *
5695
- * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
5696
- *
5697
- * @private internal function of `promptbookCli`
5698
- */
5699
- function $initializeLoginCommand(program) {
5700
- const loginCommand = program.command('login');
5701
- loginCommand.description(spaceTrim$2(`
5702
- Login to the remote Promptbook server
5703
- `));
5704
- loginCommand.action(handleActionErrors(async (cliOptions) => {
5705
- // Note: Not interested in return value of this function but the side effect of logging in
5706
- await $provideLlmToolsForCli({
5707
- isLoginloaded: true,
5708
- cliOptions: {
5709
- ...cliOptions,
5710
- strategy: 'REMOTE_SERVER', // <- Note: Overriding strategy to `REMOTE_SERVER`
5711
- // TODO: Do not allow flag `--strategy` in `login` command at all
5712
- },
5713
- });
5714
- return process.exit(0);
5715
- }));
5716
- }
5717
- /**
5718
- * TODO: Implement non-interactive login
5719
- * Note: [💞] Ignore a discrepancy between file name and entity name
5720
- * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5721
- */
5722
-
5723
- /**
5724
- * Tests if given string is valid semantic version
5725
- *
5726
- * Note: There are two similar functions:
5727
- * - `isValidSemanticVersion` which tests any semantic version
5728
- * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
5729
- *
5730
- * @public exported from `@promptbook/utils`
5731
- */
5732
- function isValidSemanticVersion(version) {
5733
- if (typeof version !== 'string') {
5734
- return false;
5735
- }
5736
- if (version.startsWith('0.0.0')) {
5737
- return false;
5738
- }
5739
- return /^\d+\.\d+\.\d+(-\d+)?$/i.test(version);
5740
- }
5741
-
5742
- /**
5743
- * Tests if given string is valid promptbook version
5744
- * It looks into list of known promptbook versions.
5745
- *
5746
- * @see https://www.npmjs.com/package/promptbook?activeTab=versions
5747
- * 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.
5748
- * Note: There are two similar functions:
5749
- * - `isValidSemanticVersion` which tests any semantic version
5750
- * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
5751
- *
5752
- * @public exported from `@promptbook/utils`
6566
+ * Note: [💞] Ignore a discrepancy between file name and entity name
6567
+ * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5753
6568
  */
5754
- function isValidPromptbookVersion(version) {
5755
- if (!isValidSemanticVersion(version)) {
5756
- return false;
5757
- }
5758
- if ( /* version === '1.0.0' || */version === '2.0.0' || version === '3.0.0') {
5759
- return false;
5760
- }
5761
- // <- TODO: [main] !!3 Check isValidPromptbookVersion against PROMPTBOOK_ENGINE_VERSIONS
5762
- return true;
5763
- }
5764
6569
 
5765
6570
  /**
5766
- * Tests if given string is valid pipeline URL URL.
6571
+ * Initializes `login` command for Promptbook CLI utilities
5767
6572
  *
5768
- * Note: There are two similar functions:
5769
- * - `isValidUrl` which tests any URL
5770
- * - `isValidPipelineUrl` *(this one)* which tests just pipeline URL
6573
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
5771
6574
  *
5772
- * @public exported from `@promptbook/utils`
6575
+ * @private internal function of `promptbookCli`
5773
6576
  */
5774
- function isValidPipelineUrl(url) {
5775
- if (!isValidUrl(url)) {
5776
- return false;
5777
- }
5778
- if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
5779
- return false;
5780
- }
5781
- if (url.includes('#')) {
5782
- // TODO: [🐠]
5783
- return false;
5784
- }
5785
- /*
5786
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
5787
- if (isUrlOnPrivateNetwork(url)) {
5788
- return false;
5789
- }
5790
- */
5791
- return true;
6577
+ function $initializeLoginCommand(program) {
6578
+ const loginCommand = program.command('login');
6579
+ loginCommand.description(spaceTrim$2(`
6580
+ Login to the remote Promptbook server
6581
+ `));
6582
+ loginCommand.action(handleActionErrors(async (cliOptions) => {
6583
+ // Note: Not interested in return value of this function but the side effect of logging in
6584
+ await $provideLlmToolsForCli({
6585
+ isLoginloaded: true,
6586
+ cliOptions: {
6587
+ ...cliOptions,
6588
+ strategy: 'REMOTE_SERVER', // <- Note: Overriding strategy to `REMOTE_SERVER`
6589
+ // TODO: Do not allow flag `--strategy` in `login` command at all
6590
+ },
6591
+ });
6592
+ return process.exit(0);
6593
+ }));
5792
6594
  }
5793
6595
  /**
5794
- * TODO: [🐠] Maybe more info why the URL is invalid
6596
+ * TODO: Implement non-interactive login
6597
+ * Note: [💞] Ignore a discrepancy between file name and entity name
6598
+ * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
5795
6599
  */
5796
6600
 
5797
6601
  /**
@@ -6494,65 +7298,6 @@ function isPipelinePrepared(pipeline) {
6494
7298
  * - [♨] Are tasks prepared
6495
7299
  */
6496
7300
 
6497
- /**
6498
- * Serializes an error into a [🚉] JSON-serializable object
6499
- *
6500
- * @public exported from `@promptbook/utils`
6501
- */
6502
- function serializeError(error) {
6503
- const { name, message, stack } = error;
6504
- const { id } = error;
6505
- if (!Object.keys(ALL_ERRORS).includes(name)) {
6506
- console.error(spaceTrim$2((block) => `
6507
-
6508
- Cannot serialize error with name "${name}"
6509
-
6510
- Authors of Promptbook probably forgot to add this error into the list of errors:
6511
- https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
6512
-
6513
-
6514
- ${block(stack || message)}
6515
-
6516
- `));
6517
- }
6518
- return {
6519
- name: name,
6520
- message,
6521
- stack,
6522
- id, // Include id in the serialized object
6523
- };
6524
- }
6525
-
6526
- /**
6527
- * Recursively converts JSON strings to JSON objects
6528
-
6529
- * @public exported from `@promptbook/utils`
6530
- */
6531
- function jsonStringsToJsons(object) {
6532
- if (object === null) {
6533
- return object;
6534
- }
6535
- if (Array.isArray(object)) {
6536
- return object.map(jsonStringsToJsons);
6537
- }
6538
- if (typeof object !== 'object') {
6539
- return object;
6540
- }
6541
- const newObject = { ...object };
6542
- for (const [key, value] of Object.entries(object)) {
6543
- if (typeof value === 'string' && isValidJsonString(value)) {
6544
- newObject[key] = jsonParse(value);
6545
- }
6546
- else {
6547
- newObject[key] = jsonStringsToJsons(value);
6548
- }
6549
- }
6550
- return newObject;
6551
- }
6552
- /**
6553
- * TODO: Type the return type correctly
6554
- */
6555
-
6556
7301
  /**
6557
7302
  * Asserts that the execution of a Promptbook is successful
6558
7303
  *
@@ -6730,144 +7475,63 @@ function createTask(options) {
6730
7475
  message = `Working on ${current.title}`;
6731
7476
  }
6732
7477
  }
6733
- if (!message) {
6734
- if (errors.length) {
6735
- message = errors[errors.length - 1].message || 'Error';
6736
- }
6737
- else if (warnings.length) {
6738
- message = warnings[warnings.length - 1].message || 'Warning';
6739
- }
6740
- else if (status === 'FINISHED') {
6741
- message = 'Finished';
6742
- }
6743
- else if (status === 'ERROR') {
6744
- message = 'Error';
6745
- }
6746
- else {
6747
- message = 'Running';
6748
- }
6749
- }
6750
- }
6751
- return {
6752
- percent: percent,
6753
- message: message + ' (!!!fallback)',
6754
- };
6755
- },
6756
- get createdAt() {
6757
- return createdAt;
6758
- // <- Note: [1] --||--
6759
- },
6760
- get updatedAt() {
6761
- return updatedAt;
6762
- // <- Note: [1] --||--
6763
- },
6764
- asPromise,
6765
- asObservable() {
6766
- return partialResultSubject.asObservable();
6767
- },
6768
- get errors() {
6769
- return errors;
6770
- // <- Note: [1] --||--
6771
- },
6772
- get warnings() {
6773
- return warnings;
6774
- // <- Note: [1] --||--
6775
- },
6776
- get llmCalls() {
6777
- return [...llmCalls, { foo: '!!! bar' }];
6778
- // <- Note: [1] --||--
6779
- },
6780
- get currentValue() {
6781
- return currentValue;
6782
- // <- Note: [1] --||--
6783
- },
6784
- };
6785
- }
6786
- /**
6787
- * TODO: Maybe allow to terminate the task and add getter `isFinished` or `status`
6788
- * TODO: [🐚] Split into more files and make `PrepareTask` & `RemoteTask` + split the function
6789
- */
6790
-
6791
- /**
6792
- * Format either small or big number
6793
- *
6794
- * @public exported from `@promptbook/utils`
6795
- */
6796
- function numberToString(value) {
6797
- if (value === 0) {
6798
- return '0';
6799
- }
6800
- else if (Number.isNaN(value)) {
6801
- return VALUE_STRINGS.nan;
6802
- }
6803
- else if (value === Infinity) {
6804
- return VALUE_STRINGS.infinity;
6805
- }
6806
- else if (value === -Infinity) {
6807
- return VALUE_STRINGS.negativeInfinity;
6808
- }
6809
- for (let exponent = 0; exponent < 15; exponent++) {
6810
- const factor = 10 ** exponent;
6811
- const valueRounded = Math.round(value * factor) / factor;
6812
- if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
6813
- return valueRounded.toFixed(exponent);
6814
- }
6815
- }
6816
- return value.toString();
6817
- }
6818
-
6819
- /**
6820
- * Function `valueToString` will convert the given value to string
6821
- * This is useful and used in the `templateParameters` function
6822
- *
6823
- * Note: This function is not just calling `toString` method
6824
- * It's more complex and can handle this conversion specifically for LLM models
6825
- * See `VALUE_STRINGS`
6826
- *
6827
- * Note: There are 2 similar functions
6828
- * - `valueToString` converts value to string for LLM models as human-readable string
6829
- * - `asSerializable` converts value to string to preserve full information to be able to convert it back
6830
- *
6831
- * @public exported from `@promptbook/utils`
6832
- */
6833
- function valueToString(value) {
6834
- try {
6835
- if (value === '') {
6836
- return VALUE_STRINGS.empty;
6837
- }
6838
- else if (value === null) {
6839
- return VALUE_STRINGS.null;
6840
- }
6841
- else if (value === undefined) {
6842
- return VALUE_STRINGS.undefined;
6843
- }
6844
- else if (typeof value === 'string') {
6845
- return value;
6846
- }
6847
- else if (typeof value === 'number') {
6848
- return numberToString(value);
6849
- }
6850
- else if (value instanceof Date) {
6851
- return value.toISOString();
6852
- }
6853
- else {
6854
- try {
6855
- return JSON.stringify(value);
6856
- }
6857
- catch (error) {
6858
- if (error instanceof TypeError && error.message.includes('circular structure')) {
6859
- return VALUE_STRINGS.circular;
7478
+ if (!message) {
7479
+ if (errors.length) {
7480
+ message = errors[errors.length - 1].message || 'Error';
7481
+ }
7482
+ else if (warnings.length) {
7483
+ message = warnings[warnings.length - 1].message || 'Warning';
7484
+ }
7485
+ else if (status === 'FINISHED') {
7486
+ message = 'Finished';
7487
+ }
7488
+ else if (status === 'ERROR') {
7489
+ message = 'Error';
7490
+ }
7491
+ else {
7492
+ message = 'Running';
7493
+ }
6860
7494
  }
6861
- throw error;
6862
7495
  }
6863
- }
6864
- }
6865
- catch (error) {
6866
- assertsError(error);
6867
- console.error(error);
6868
- return VALUE_STRINGS.unserializable;
6869
- }
7496
+ return {
7497
+ percent: percent,
7498
+ message: message + ' (!!!fallback)',
7499
+ };
7500
+ },
7501
+ get createdAt() {
7502
+ return createdAt;
7503
+ // <- Note: [1] --||--
7504
+ },
7505
+ get updatedAt() {
7506
+ return updatedAt;
7507
+ // <- Note: [1] --||--
7508
+ },
7509
+ asPromise,
7510
+ asObservable() {
7511
+ return partialResultSubject.asObservable();
7512
+ },
7513
+ get errors() {
7514
+ return errors;
7515
+ // <- Note: [1] --||--
7516
+ },
7517
+ get warnings() {
7518
+ return warnings;
7519
+ // <- Note: [1] --||--
7520
+ },
7521
+ get llmCalls() {
7522
+ return [...llmCalls, { foo: '!!! bar' }];
7523
+ // <- Note: [1] --||--
7524
+ },
7525
+ get currentValue() {
7526
+ return currentValue;
7527
+ // <- Note: [1] --||--
7528
+ },
7529
+ };
6870
7530
  }
7531
+ /**
7532
+ * TODO: Maybe allow to terminate the task and add getter `isFinished` or `status`
7533
+ * TODO: [🐚] Split into more files and make `PrepareTask` & `RemoteTask` + split the function
7534
+ */
6871
7535
 
6872
7536
  /**
6873
7537
  * Parses the given script and returns the list of all used variables that are not defined in the script
@@ -6994,41 +7658,6 @@ function extractParameterNamesFromTask(task) {
6994
7658
  * TODO: [🔣] If script require contentLanguage
6995
7659
  */
6996
7660
 
6997
- /**
6998
- * Create difference set of two sets.
6999
- *
7000
- * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
7001
- * @public exported from `@promptbook/utils`
7002
- */
7003
- function difference(a, b, isEqual = (a, b) => a === b) {
7004
- const diff = new Set();
7005
- for (const itemA of Array.from(a)) {
7006
- if (!Array.from(b).some((itemB) => isEqual(itemA, itemB))) {
7007
- diff.add(itemA);
7008
- }
7009
- }
7010
- return diff;
7011
- }
7012
- /**
7013
- * TODO: [🧠][💯] Maybe also implement symmetricDifference
7014
- */
7015
-
7016
- /**
7017
- * Creates a new set with all elements that are present in either set
7018
- *
7019
- * @deprecated use new javascript set methods instead @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
7020
- * @public exported from `@promptbook/utils`
7021
- */
7022
- function union(...sets) {
7023
- const union = new Set();
7024
- for (const set of sets) {
7025
- for (const item of Array.from(set)) {
7026
- union.add(item);
7027
- }
7028
- }
7029
- return union;
7030
- }
7031
-
7032
7661
  /**
7033
7662
  * Contains configuration options for parsing and generating CSV files, such as delimiters and quoting rules.
7034
7663
  *
@@ -7057,28 +7686,6 @@ function csvParse(value /* <- TODO: string_csv */, settings, schema /* <- TODO:
7057
7686
  return csv;
7058
7687
  }
7059
7688
 
7060
- /**
7061
- * Function to check if a string is valid CSV
7062
- *
7063
- * @param value The string to check
7064
- * @returns `true` if the string is a valid CSV string, false otherwise
7065
- *
7066
- * @public exported from `@promptbook/utils`
7067
- */
7068
- function isValidCsvString(value) {
7069
- try {
7070
- // A simple check for CSV format: at least one comma and no invalid characters
7071
- if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
7072
- return true;
7073
- }
7074
- return false;
7075
- }
7076
- catch (error) {
7077
- assertsError(error);
7078
- return false;
7079
- }
7080
- }
7081
-
7082
7689
  /**
7083
7690
  * Definition for CSV spreadsheet
7084
7691
  *
@@ -7258,30 +7865,6 @@ const TextFormatParser = {
7258
7865
  * TODO: [🏢] Allow to expect something inside each item of list and other formats
7259
7866
  */
7260
7867
 
7261
- /**
7262
- * Function to check if a string is valid XML
7263
- *
7264
- * @param value
7265
- * @returns `true` if the string is a valid XML string, false otherwise
7266
- *
7267
- * @public exported from `@promptbook/utils`
7268
- */
7269
- function isValidXmlString(value) {
7270
- try {
7271
- const parser = new DOMParser();
7272
- const parsedDocument = parser.parseFromString(value, 'application/xml');
7273
- const parserError = parsedDocument.getElementsByTagName('parsererror');
7274
- if (parserError.length > 0) {
7275
- return false;
7276
- }
7277
- return true;
7278
- }
7279
- catch (error) {
7280
- assertsError(error);
7281
- return false;
7282
- }
7283
- }
7284
-
7285
7868
  /**
7286
7869
  * Definition for XML format
7287
7870
  *
@@ -7371,130 +7954,59 @@ function mapAvailableToExpectedParameters(options) {
7371
7954
  ${block(Array.from(expectedParameterNames)
7372
7955
  .map((parameterName) => `- {${parameterName}}`)
7373
7956
  .join('\n'))}
7374
-
7375
- Remaining available parameters:
7376
- ${block(Array.from(availableParametersNames)
7377
- .map((parameterName) => `- {${parameterName}}`)
7378
- .join('\n'))}
7379
-
7380
- `));
7381
- }
7382
- const expectedParameterNamesArray = Array.from(expectedParameterNames);
7383
- const availableParametersNamesArray = Array.from(availableParametersNames);
7384
- for (let i = 0; i < expectedParameterNames.size; i++) {
7385
- mappedParameters[expectedParameterNamesArray[i]] = availableParameters[availableParametersNamesArray[i]];
7386
- }
7387
- // Note: [👨‍👨‍👧] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
7388
- Object.freeze(mappedParameters);
7389
- return mappedParameters;
7390
- }
7391
-
7392
- /**
7393
- * Takes an item or an array of items and returns an array of items
7394
- *
7395
- * 1) Any item except array and undefined returns array with that one item (also null)
7396
- * 2) Undefined returns empty array
7397
- * 3) Array returns itself
7398
- *
7399
- * @private internal utility
7400
- */
7401
- function arrayableToArray(input) {
7402
- if (input === undefined) {
7403
- return [];
7404
- }
7405
- if (input instanceof Array) {
7406
- return input;
7407
- }
7408
- return [input];
7409
- }
7410
-
7411
- /**
7412
- * Just returns the given `LlmExecutionTools` or joins multiple into one
7413
- *
7414
- * @public exported from `@promptbook/core`
7415
- */
7416
- function getSingleLlmExecutionTools(oneOrMoreLlmExecutionTools) {
7417
- const _llms = arrayableToArray(oneOrMoreLlmExecutionTools);
7418
- const llmTools = _llms.length === 1
7419
- ? _llms[0]
7420
- : joinLlmExecutionTools('Multiple LLM Providers joined by `getSingleLlmExecutionTools`', ..._llms);
7421
- return llmTools;
7422
- }
7423
- /**
7424
- * TODO: [🙆] `getSingleLlmExecutionTools` vs `joinLlmExecutionTools` - explain difference or pick one
7425
- * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
7426
- */
7427
-
7428
- /**
7429
- * Replaces parameters in template with values from parameters object
7430
- *
7431
- * Note: This function is not places strings into string,
7432
- * It's more complex and can handle this operation specifically for LLM models
7433
- *
7434
- * @param template the template with parameters in {curly} braces
7435
- * @param parameters the object with parameters
7436
- * @returns the template with replaced parameters
7437
- * @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
7438
- * @public exported from `@promptbook/utils`
7439
- */
7440
- function templateParameters(template, parameters) {
7441
- for (const [parameterName, parameterValue] of Object.entries(parameters)) {
7442
- if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
7443
- throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
7444
- }
7445
- else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
7446
- // TODO: [🍵]
7447
- throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
7448
- }
7449
- }
7450
- let replacedTemplates = template;
7451
- let match;
7452
- let loopLimit = LOOP_LIMIT;
7453
- while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
7454
- .exec(replacedTemplates))) {
7455
- if (loopLimit-- < 0) {
7456
- throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
7457
- }
7458
- const precol = match.groups.precol;
7459
- const parameterName = match.groups.parameterName;
7460
- if (parameterName === '') {
7461
- // Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
7462
- continue;
7463
- }
7464
- if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
7465
- throw new PipelineExecutionError('Parameter is already opened or not closed');
7466
- }
7467
- if (parameters[parameterName] === undefined) {
7468
- throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
7469
- }
7470
- let parameterValue = parameters[parameterName];
7471
- if (parameterValue === undefined) {
7472
- throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
7473
- }
7474
- parameterValue = valueToString(parameterValue);
7475
- // Escape curly braces in parameter values to prevent prompt-injection
7476
- parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
7477
- if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
7478
- parameterValue = parameterValue
7479
- .split('\n')
7480
- .map((line, index) => (index === 0 ? line : `${precol}${line}`))
7481
- .join('\n');
7482
- }
7483
- replacedTemplates =
7484
- replacedTemplates.substring(0, match.index + precol.length) +
7485
- parameterValue +
7486
- replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
7957
+
7958
+ Remaining available parameters:
7959
+ ${block(Array.from(availableParametersNames)
7960
+ .map((parameterName) => `- {${parameterName}}`)
7961
+ .join('\n'))}
7962
+
7963
+ `));
7487
7964
  }
7488
- // [💫] Check if there are parameters that are not closed properly
7489
- if (/{\w+$/.test(replacedTemplates)) {
7490
- throw new PipelineExecutionError('Parameter is not closed');
7965
+ const expectedParameterNamesArray = Array.from(expectedParameterNames);
7966
+ const availableParametersNamesArray = Array.from(availableParametersNames);
7967
+ for (let i = 0; i < expectedParameterNames.size; i++) {
7968
+ mappedParameters[expectedParameterNamesArray[i]] = availableParameters[availableParametersNamesArray[i]];
7491
7969
  }
7492
- // [💫] Check if there are parameters that are not opened properly
7493
- if (/^\w+}/.test(replacedTemplates)) {
7494
- throw new PipelineExecutionError('Parameter is not opened');
7970
+ // Note: [👨‍👨‍👧] Now we can freeze `mappedParameters` to prevent accidental modifications after mapping
7971
+ Object.freeze(mappedParameters);
7972
+ return mappedParameters;
7973
+ }
7974
+
7975
+ /**
7976
+ * Takes an item or an array of items and returns an array of items
7977
+ *
7978
+ * 1) Any item except array and undefined returns array with that one item (also null)
7979
+ * 2) Undefined returns empty array
7980
+ * 3) Array returns itself
7981
+ *
7982
+ * @private internal utility
7983
+ */
7984
+ function arrayableToArray(input) {
7985
+ if (input === undefined) {
7986
+ return [];
7495
7987
  }
7496
- return replacedTemplates;
7988
+ if (input instanceof Array) {
7989
+ return input;
7990
+ }
7991
+ return [input];
7992
+ }
7993
+
7994
+ /**
7995
+ * Just returns the given `LlmExecutionTools` or joins multiple into one
7996
+ *
7997
+ * @public exported from `@promptbook/core`
7998
+ */
7999
+ function getSingleLlmExecutionTools(oneOrMoreLlmExecutionTools) {
8000
+ const _llms = arrayableToArray(oneOrMoreLlmExecutionTools);
8001
+ const llmTools = _llms.length === 1
8002
+ ? _llms[0]
8003
+ : joinLlmExecutionTools('Multiple LLM Providers joined by `getSingleLlmExecutionTools`', ..._llms);
8004
+ return llmTools;
7497
8005
  }
8006
+ /**
8007
+ * TODO: [🙆] `getSingleLlmExecutionTools` vs `joinLlmExecutionTools` - explain difference or pick one
8008
+ * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
8009
+ */
7498
8010
 
7499
8011
  /**
7500
8012
  * Executes a pipeline task with multiple attempts, including joker and retry logic. Handles different task types
@@ -7590,8 +8102,9 @@ async function executeAttempts(options) {
7590
8102
  $ongoingTaskResult.$resultString = $ongoingTaskResult.$completionResult.content;
7591
8103
  break variant;
7592
8104
  case 'EMBEDDING':
8105
+ case 'IMAGE_GENERATION':
7593
8106
  throw new PipelineExecutionError(spaceTrim$1((block) => `
7594
- Embedding model can not be used in pipeline
8107
+ ${modelRequirements.modelVariant} model can not be used in pipeline
7595
8108
 
7596
8109
  This should be catched during parsing
7597
8110
 
@@ -8725,35 +9238,6 @@ function createPipelineExecutor(options) {
8725
9238
  return pipelineExecutor;
8726
9239
  }
8727
9240
 
8728
- /**
8729
- * Async version of Array.forEach
8730
- *
8731
- * @param array - Array to iterate over
8732
- * @param options - Options for the function
8733
- * @param callbackfunction - Function to call for each item
8734
- * @public exported from `@promptbook/utils`
8735
- * @deprecated [🪂] Use queues instead
8736
- */
8737
- async function forEachAsync(array, options, callbackfunction) {
8738
- const { maxParallelCount = Infinity } = options;
8739
- let index = 0;
8740
- let runningTasks = [];
8741
- const tasks = [];
8742
- for (const item of array) {
8743
- const currentIndex = index++;
8744
- const task = callbackfunction(item, currentIndex, array);
8745
- tasks.push(task);
8746
- runningTasks.push(task);
8747
- /* not await */ Promise.resolve(task).then(() => {
8748
- runningTasks = runningTasks.filter((t) => t !== task);
8749
- });
8750
- if (maxParallelCount < runningTasks.length) {
8751
- await Promise.race(runningTasks);
8752
- }
8753
- }
8754
- await Promise.all(tasks);
8755
- }
8756
-
8757
9241
  /**
8758
9242
  * Prepares the persona for the pipeline
8759
9243
  *
@@ -9889,75 +10373,6 @@ const EXPECTATION_UNITS = ['CHARACTERS', 'WORDS', 'SENTENCES', 'LINES', 'PARAGRA
9889
10373
  * TODO: [💝] Unite object for expecting amount and format - remove format
9890
10374
  */
9891
10375
 
9892
- /**
9893
- * Function parseNumber will parse number from string
9894
- *
9895
- * Note: [🔂] This function is idempotent.
9896
- * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
9897
- * Note: it also works only with decimal numbers
9898
- *
9899
- * @returns parsed number
9900
- * @throws {ParseError} if the value is not a number
9901
- *
9902
- * @public exported from `@promptbook/utils`
9903
- */
9904
- function parseNumber(value) {
9905
- const originalValue = value;
9906
- if (typeof value === 'number') {
9907
- value = value.toString(); // <- TODO: Maybe more efficient way to do this
9908
- }
9909
- if (typeof value !== 'string') {
9910
- return 0;
9911
- }
9912
- value = value.trim();
9913
- if (value.startsWith('+')) {
9914
- return parseNumber(value.substring(1));
9915
- }
9916
- if (value.startsWith('-')) {
9917
- const number = parseNumber(value.substring(1));
9918
- if (number === 0) {
9919
- return 0; // <- Note: To prevent -0
9920
- }
9921
- return -number;
9922
- }
9923
- value = value.replace(/,/g, '.');
9924
- value = value.toUpperCase();
9925
- if (value === '') {
9926
- return 0;
9927
- }
9928
- if (value === '♾' || value.startsWith('INF')) {
9929
- return Infinity;
9930
- }
9931
- if (value.includes('/')) {
9932
- const [numerator_, denominator_] = value.split('/');
9933
- const numerator = parseNumber(numerator_);
9934
- const denominator = parseNumber(denominator_);
9935
- if (denominator === 0) {
9936
- throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
9937
- }
9938
- return numerator / denominator;
9939
- }
9940
- if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
9941
- return 0;
9942
- }
9943
- if (value.includes('E')) {
9944
- const [significand, exponent] = value.split('E');
9945
- return parseNumber(significand) * 10 ** parseNumber(exponent);
9946
- }
9947
- if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
9948
- throw new ParseError(`Unable to parse number from "${originalValue}"`);
9949
- }
9950
- const num = parseFloat(value);
9951
- if (isNaN(num)) {
9952
- throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
9953
- }
9954
- return num;
9955
- }
9956
- /**
9957
- * TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
9958
- * TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
9959
- */
9960
-
9961
10376
  /**
9962
10377
  import { WrappedError } from '../../errors/WrappedError';
9963
10378
  import { assertsError } from '../../errors/assertsError';
@@ -10100,84 +10515,6 @@ const expectCommandParser = {
10100
10515
  },
10101
10516
  };
10102
10517
 
10103
- /**
10104
- * Normalizes a given text to camelCase format.
10105
- *
10106
- * Note: [🔂] This function is idempotent.
10107
- *
10108
- * @param text The text to be normalized.
10109
- * @param _isFirstLetterCapital Whether the first letter should be capitalized.
10110
- * @returns The camelCase formatted string.
10111
- * @example 'helloWorld'
10112
- * @example 'iLovePromptbook'
10113
- * @public exported from `@promptbook/utils`
10114
- */
10115
- function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
10116
- let charType;
10117
- let lastCharType = null;
10118
- let normalizedName = '';
10119
- for (const char of text) {
10120
- let normalizedChar;
10121
- if (/^[a-z]$/.test(char)) {
10122
- charType = 'LOWERCASE';
10123
- normalizedChar = char;
10124
- }
10125
- else if (/^[A-Z]$/.test(char)) {
10126
- charType = 'UPPERCASE';
10127
- normalizedChar = char.toLowerCase();
10128
- }
10129
- else if (/^[0-9]$/.test(char)) {
10130
- charType = 'NUMBER';
10131
- normalizedChar = char;
10132
- }
10133
- else {
10134
- charType = 'OTHER';
10135
- normalizedChar = '';
10136
- }
10137
- if (!lastCharType) {
10138
- if (_isFirstLetterCapital) {
10139
- normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
10140
- }
10141
- }
10142
- else if (charType !== lastCharType &&
10143
- !(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
10144
- !(lastCharType === 'NUMBER') &&
10145
- !(charType === 'NUMBER')) {
10146
- normalizedChar = normalizedChar.toUpperCase(); //TODO: [🌺] DRY
10147
- }
10148
- normalizedName += normalizedChar;
10149
- lastCharType = charType;
10150
- }
10151
- return normalizedName;
10152
- }
10153
- /**
10154
- * TODO: [🌺] Use some intermediate util splitWords
10155
- */
10156
-
10157
- /**
10158
- * Removes quotes from a string
10159
- *
10160
- * Note: [🔂] This function is idempotent.
10161
- * Tip: This is very useful for post-processing of the result of the LLM model
10162
- * Note: This function removes only the same quotes from the beginning and the end of the string
10163
- * Note: There are two similar functions:
10164
- * - `removeQuotes` which removes only bounding quotes
10165
- * - `unwrapResult` which removes whole introduce sentence
10166
- *
10167
- * @param text optionally quoted text
10168
- * @returns text without quotes
10169
- * @public exported from `@promptbook/utils`
10170
- */
10171
- function removeQuotes(text) {
10172
- if (text.startsWith('"') && text.endsWith('"')) {
10173
- return text.slice(1, -1);
10174
- }
10175
- if (text.startsWith("'") && text.endsWith("'")) {
10176
- return text.slice(1, -1);
10177
- }
10178
- return text;
10179
- }
10180
-
10181
10518
  /**
10182
10519
  * Function `validateParameterName` will normalize and validate a parameter name for use in pipelines.
10183
10520
  * It removes diacritics, emojis, and quotes, normalizes to camelCase, and checks for reserved names and invalid characters.
@@ -11067,11 +11404,7 @@ const modelCommandParser = {
11067
11404
  // TODO: [🚜] DRY
11068
11405
  if ($taskJson.modelRequirements[command.key] !== undefined) {
11069
11406
  if ($taskJson.modelRequirements[command.key] === command.value) {
11070
- console.warn(`Multiple commands \`MODEL ${{
11071
- modelName: 'NAME',
11072
- modelVariant: 'VARIANT',
11073
- maxTokens: '???',
11074
- }[command.key]} ${command.value}\` in the task "${$taskJson.title || $taskJson.name}"`);
11407
+ console.warn(`Multiple commands \`MODEL ${command.key} ${command.value}\` in the task "${$taskJson.title || $taskJson.name}"`);
11075
11408
  // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
11076
11409
  }
11077
11410
  else {
@@ -11352,30 +11685,16 @@ function $applyToTaskJson(command, $taskJson, $pipelineJson) {
11352
11685
  }
11353
11686
  console.warn(spaceTrim$2(`
11354
11687
 
11355
- Persona "${personaName}" is defined multiple times with different description:
11356
-
11357
- First definition:
11358
- ${persona.description}
11359
-
11360
- Second definition:
11361
- ${personaDescription}
11362
-
11363
- `));
11364
- persona.description += spaceTrim$2('\n\n' + personaDescription);
11365
- }
11366
-
11367
- /**
11368
- * Checks if the given value is a valid JavaScript identifier name.
11369
- *
11370
- * @param javascriptName The value to check for JavaScript identifier validity.
11371
- * @returns `true` if the value is a valid JavaScript name, false otherwise.
11372
- * @public exported from `@promptbook/utils`
11373
- */
11374
- function isValidJavascriptName(javascriptName) {
11375
- if (typeof javascriptName !== 'string') {
11376
- return false;
11377
- }
11378
- return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
11688
+ Persona "${personaName}" is defined multiple times with different description:
11689
+
11690
+ First definition:
11691
+ ${persona.description}
11692
+
11693
+ Second definition:
11694
+ ${personaDescription}
11695
+
11696
+ `));
11697
+ persona.description += spaceTrim$2('\n\n' + personaDescription);
11379
11698
  }
11380
11699
 
11381
11700
  /**
@@ -12891,345 +13210,59 @@ function parsePipeline(pipelineString) {
12891
13210
  if ($pipelineJson.formfactorName === undefined) {
12892
13211
  $pipelineJson.formfactorName = 'GENERIC';
12893
13212
  }
12894
- // =============================================================
12895
- return exportJson({
12896
- name: 'pipelineJson',
12897
- message: `Result of \`parsePipeline\``,
12898
- order: ORDER_OF_PIPELINE_JSON,
12899
- value: {
12900
- formfactorName: 'GENERIC',
12901
- // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
12902
- ...$pipelineJson,
12903
- },
12904
- });
12905
- }
12906
- /**
12907
- * TODO: [🧠] Maybe more things here can be refactored as high-level abstractions
12908
- * TODO: [main] !!4 Warn if used only sync version
12909
- * TODO: [🚞] Report here line/column of error
12910
- * TODO: Use spaceTrim more effectively
12911
- * TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
12912
- * TODO: [🥞] Not optimal parsing because `splitMarkdownIntoSections` is executed twice with same string, once through `flattenMarkdown` and second directly here
12913
- * TODO: [♈] Probably move expectations from tasks to parameters
12914
- * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
12915
- * TODO: [🍙] Make some standard order of json properties
12916
- */
12917
-
12918
- /**
12919
- * Compile pipeline from string (markdown) format to JSON format
12920
- *
12921
- * @see https://github.com/webgptorg/promptbook/discussions/196
12922
- *
12923
- * Note: This function does not validate logic of the pipeline only the parsing
12924
- * Note: This function acts as compilation process
12925
- *
12926
- * @param pipelineString {Promptbook} in string markdown format (.book.md)
12927
- * @param tools - Tools for the preparation and scraping - if not provided together with `llm`, the preparation will be skipped
12928
- * @param options - Options and tools for the compilation
12929
- * @returns {Promptbook} compiled in JSON format (.bookc)
12930
- * @throws {ParseError} if the promptbook string is not valid
12931
- * @public exported from `@promptbook/core`
12932
- */
12933
- async function compilePipeline(pipelineString, tools, options) {
12934
- let pipelineJson = parsePipeline(pipelineString);
12935
- if (tools !== undefined && tools.llm !== undefined) {
12936
- pipelineJson = await preparePipeline(pipelineJson, tools, options || {
12937
- rootDirname: null,
12938
- });
12939
- }
12940
- // Note: No need to use `$exportJson` because `parsePipeline` and `preparePipeline` already do that
12941
- return pipelineJson;
12942
- }
12943
- /**
12944
- * TODO: [🏏] Leverage the batch API and build queues @see https://platform.openai.com/docs/guides/batch
12945
- * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
12946
- * TODO: [🧠] Should be in generated JSON file GENERATOR_WARNING
12947
- */
12948
-
12949
- /**
12950
- * Creates a Mermaid graph based on the promptbook
12951
- *
12952
- * Note: The result is not wrapped in a Markdown code block
12953
- *
12954
- * @public exported from `@promptbook/utils`
12955
- */
12956
- function renderPromptbookMermaid(pipelineJson, options) {
12957
- const { linkTask = () => null } = options || {};
12958
- const MERMAID_PREFIX = 'pipeline_';
12959
- const MERMAID_KNOWLEDGE_NAME = MERMAID_PREFIX + 'knowledge';
12960
- const MERMAID_RESERVED_NAME = MERMAID_PREFIX + 'reserved';
12961
- const MERMAID_INPUT_NAME = MERMAID_PREFIX + 'input';
12962
- const MERMAID_OUTPUT_NAME = MERMAID_PREFIX + 'output';
12963
- const parameterNameToTaskName = (parameterName) => {
12964
- if (parameterName === 'knowledge') {
12965
- return MERMAID_KNOWLEDGE_NAME;
12966
- }
12967
- else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
12968
- return MERMAID_RESERVED_NAME;
12969
- }
12970
- const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
12971
- if (!parameter) {
12972
- throw new UnexpectedError(`Could not find {${parameterName}}`);
12973
- // <- TODO: This causes problems when {knowledge} and other reserved parameters are used
12974
- }
12975
- if (parameter.isInput) {
12976
- return MERMAID_INPUT_NAME;
12977
- }
12978
- const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName);
12979
- if (!task) {
12980
- throw new Error(`Could not find task for {${parameterName}}`);
12981
- }
12982
- return MERMAID_PREFIX + (task.name || normalizeTo_camelCase('task-' + titleToName(task.title)));
12983
- };
12984
- const inputAndIntermediateParametersMermaid = pipelineJson.tasks
12985
- .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
12986
- `${parameterNameToTaskName(resultingParameterName)}("${title}")`,
12987
- ...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`),
12988
- ])
12989
- .join('\n');
12990
- const outputParametersMermaid = pipelineJson.parameters
12991
- .filter(({ isOutput }) => isOutput)
12992
- .map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->${MERMAID_OUTPUT_NAME}`)
12993
- .join('\n');
12994
- const linksMermaid = pipelineJson.tasks
12995
- .map((task) => {
12996
- const link = linkTask(task);
12997
- if (link === null) {
12998
- return '';
12999
- }
13000
- const { href, title } = link;
13001
- const taskName = parameterNameToTaskName(task.resultingParameterName);
13002
- return `click ${taskName} href "${href}" "${title}";`;
13003
- })
13004
- .filter((line) => line !== '')
13005
- .join('\n');
13006
- const interactionPointsMermaid = Object.entries({
13007
- [MERMAID_INPUT_NAME]: 'Input',
13008
- [MERMAID_OUTPUT_NAME]: 'Output',
13009
- [MERMAID_RESERVED_NAME]: 'Other',
13010
- [MERMAID_KNOWLEDGE_NAME]: 'Knowledge',
13011
- })
13012
- .filter(([MERMAID_NAME]) => (inputAndIntermediateParametersMermaid + outputParametersMermaid).includes(MERMAID_NAME))
13013
- .map(([MERMAID_NAME, title]) => `${MERMAID_NAME}((${title})):::${MERMAID_NAME}`)
13014
- .join('\n');
13015
- const promptbookMermaid = spaceTrim$1((block) => `
13016
-
13017
- %% 🔮 Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually
13018
-
13019
- flowchart LR
13020
- subgraph "${pipelineJson.title}"
13021
-
13022
- %% Basic configuration
13023
- direction TB
13024
-
13025
- %% Interaction points from pipeline to outside
13026
- ${block(interactionPointsMermaid)}
13027
-
13028
- %% Input and intermediate parameters
13029
- ${block(inputAndIntermediateParametersMermaid)}
13030
-
13031
-
13032
- %% Output parameters
13033
- ${block(outputParametersMermaid)}
13034
-
13035
- %% Links
13036
- ${block(linksMermaid)}
13037
-
13038
- %% Styles
13039
- classDef ${MERMAID_INPUT_NAME} color: grey;
13040
- classDef ${MERMAID_OUTPUT_NAME} color: grey;
13041
- classDef ${MERMAID_RESERVED_NAME} color: grey;
13042
- classDef ${MERMAID_KNOWLEDGE_NAME} color: grey;
13043
-
13044
- end;
13045
-
13046
- `);
13047
- return promptbookMermaid;
13048
- }
13049
- /**
13050
- * TODO: [🧠] FOREACH in mermaid graph
13051
- * TODO: [🧠] Knowledge in mermaid graph
13052
- * TODO: [🧠] Personas in mermaid graph
13053
- * TODO: Maybe use some Mermaid package instead of string templating
13054
- * TODO: [🕌] When more than 2 functionalities, split into separate functions
13055
- */
13056
-
13057
- /**
13058
- * Computes SHA-256 hash of the given object
13059
- *
13060
- * @public exported from `@promptbook/utils`
13061
- */
13062
- function computeHash(value) {
13063
- return SHA256(hexEncoder.parse(spaceTrim$2(valueToString(value)))).toString( /* hex */);
13064
- }
13065
- /**
13066
- * TODO: [🥬][🥬] Use this ACRY
13067
- */
13068
-
13069
- /**
13070
- * Makes first letter of a string lowercase
13071
- *
13072
- * Note: [🔂] This function is idempotent.
13073
- *
13074
- * @public exported from `@promptbook/utils`
13075
- */
13076
- function decapitalize(word) {
13077
- return word.substring(0, 1).toLowerCase() + word.substring(1);
13078
- }
13079
-
13080
- /**
13081
- * Parses keywords from a string
13082
- *
13083
- * @param {string} input
13084
- * @returns {Set} of keywords without diacritics in lowercase
13085
- * @public exported from `@promptbook/utils`
13086
- */
13087
- function parseKeywordsFromString(input) {
13088
- const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
13089
- .toLowerCase()
13090
- .split(/[^a-z0-9]+/gs)
13091
- .filter((value) => value);
13092
- return new Set(keywords);
13093
- }
13094
-
13095
- /**
13096
- * Converts a name string into a URI-compatible format.
13097
- *
13098
- * @param name The string to be converted to a URI-compatible format.
13099
- * @returns A URI-compatible string derived from the input name.
13100
- * @example 'Hello World' -> 'hello-world'
13101
- * @public exported from `@promptbook/utils`
13102
- */
13103
- function nameToUriPart(name) {
13104
- let uriPart = name;
13105
- uriPart = uriPart.toLowerCase();
13106
- uriPart = removeDiacritics(uriPart);
13107
- uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
13108
- uriPart = uriPart.replace(/^-+/, '');
13109
- uriPart = uriPart.replace(/-+$/, '');
13110
- return uriPart;
13111
- }
13112
-
13113
- /**
13114
- * Converts a given name into URI-compatible parts.
13115
- *
13116
- * @param name The name to be converted into URI parts.
13117
- * @returns An array of URI-compatible parts derived from the name.
13118
- * @example 'Example Name' -> ['example', 'name']
13119
- * @public exported from `@promptbook/utils`
13120
- */
13121
- function nameToUriParts(name) {
13122
- return nameToUriPart(name)
13123
- .split('-')
13124
- .filter((value) => value !== '');
13125
- }
13126
-
13127
- /**
13128
- * Normalizes a given text to PascalCase format.
13129
- *
13130
- * Note: [🔂] This function is idempotent.
13131
- *
13132
- * @param text @public exported from `@promptbook/utils`
13133
- * @returns
13134
- * @example 'HelloWorld'
13135
- * @example 'ILovePromptbook'
13136
- * @public exported from `@promptbook/utils`
13137
- */
13138
- function normalizeTo_PascalCase(text) {
13139
- return normalizeTo_camelCase(text, true);
13213
+ // =============================================================
13214
+ return exportJson({
13215
+ name: 'pipelineJson',
13216
+ message: `Result of \`parsePipeline\``,
13217
+ order: ORDER_OF_PIPELINE_JSON,
13218
+ value: {
13219
+ formfactorName: 'GENERIC',
13220
+ // <- Note: [🔆] Setting `formfactorName` is redundant to satisfy the typescript
13221
+ ...$pipelineJson,
13222
+ },
13223
+ });
13140
13224
  }
13141
-
13142
13225
  /**
13143
- * Take every whitespace (space, new line, tab) and replace it with a single space
13144
- *
13145
- * Note: [🔂] This function is idempotent.
13146
- *
13147
- * @public exported from `@promptbook/utils`
13226
+ * TODO: [🧠] Maybe more things here can be refactored as high-level abstractions
13227
+ * TODO: [main] !!4 Warn if used only sync version
13228
+ * TODO: [🚞] Report here line/column of error
13229
+ * TODO: Use spaceTrim more effectively
13230
+ * TODO: [🧠] Parameter flags - isInput, isOutput, isInternal
13231
+ * TODO: [🥞] Not optimal parsing because `splitMarkdownIntoSections` is executed twice with same string, once through `flattenMarkdown` and second directly here
13232
+ * TODO: [♈] Probably move expectations from tasks to parameters
13233
+ * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
13234
+ * TODO: [🍙] Make some standard order of json properties
13148
13235
  */
13149
- function normalizeWhitespaces(sentence) {
13150
- return sentence.replace(/\s+/gs, ' ').trim();
13151
- }
13152
13236
 
13153
13237
  /**
13154
- * Adds suffix to the URL
13238
+ * Compile pipeline from string (markdown) format to JSON format
13155
13239
  *
13156
- * @public exported from `@promptbook/utils`
13157
- */
13158
- function suffixUrl(value, suffix) {
13159
- const baseUrl = value.href.endsWith('/') ? value.href.slice(0, -1) : value.href;
13160
- const normalizedSuffix = suffix.replace(/\/+/g, '/');
13161
- return (baseUrl + normalizedSuffix);
13162
- }
13163
-
13164
- /**
13165
- * Removes quotes and optional introduce text from a string
13240
+ * @see https://github.com/webgptorg/promptbook/discussions/196
13166
13241
  *
13167
- * Tip: This is very useful for post-processing of the result of the LLM model
13168
- * Note: This function trims the text and removes whole introduce sentence if it is present
13169
- * Note: There are two similar functions:
13170
- * - `removeQuotes` which removes only bounding quotes
13171
- * - `unwrapResult` which removes whole introduce sentence
13242
+ * Note: This function does not validate logic of the pipeline only the parsing
13243
+ * Note: This function acts as compilation process
13172
13244
  *
13173
- * @param text optionally quoted text
13174
- * @returns text without quotes
13175
- * @public exported from `@promptbook/utils`
13245
+ * @param pipelineString {Promptbook} in string markdown format (.book.md)
13246
+ * @param tools - Tools for the preparation and scraping - if not provided together with `llm`, the preparation will be skipped
13247
+ * @param options - Options and tools for the compilation
13248
+ * @returns {Promptbook} compiled in JSON format (.bookc)
13249
+ * @throws {ParseError} if the promptbook string is not valid
13250
+ * @public exported from `@promptbook/core`
13176
13251
  */
13177
- function unwrapResult(text, options) {
13178
- const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
13179
- let trimmedText = text;
13180
- // Remove leading and trailing spaces and newlines
13181
- if (isTrimmed) {
13182
- trimmedText = spaceTrim$1(trimmedText);
13183
- }
13184
- let processedText = trimmedText;
13185
- if (isIntroduceSentenceRemoved) {
13186
- const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
13187
- if (introduceSentenceRegex.test(text)) {
13188
- // Remove the introduce sentence and quotes by replacing it with an empty string
13189
- processedText = processedText.replace(introduceSentenceRegex, '');
13190
- }
13191
- processedText = spaceTrim$1(processedText);
13192
- }
13193
- if (processedText.length < 3) {
13194
- return trimmedText;
13195
- }
13196
- if (processedText.includes('\n')) {
13197
- return trimmedText;
13198
- }
13199
- // Remove the quotes by extracting the substring without the first and last characters
13200
- const unquotedText = processedText.slice(1, -1);
13201
- // Check if the text starts and ends with quotes
13202
- if ([
13203
- ['"', '"'],
13204
- ["'", "'"],
13205
- ['`', '`'],
13206
- ['*', '*'],
13207
- ['_', '_'],
13208
- ['„', '“'],
13209
- ['«', '»'] /* <- QUOTES to config */,
13210
- ].some(([startQuote, endQuote]) => {
13211
- if (!processedText.startsWith(startQuote)) {
13212
- return false;
13213
- }
13214
- if (!processedText.endsWith(endQuote)) {
13215
- return false;
13216
- }
13217
- if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
13218
- return false;
13219
- }
13220
- if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
13221
- return false;
13222
- }
13223
- return true;
13224
- })) {
13225
- return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
13226
- }
13227
- else {
13228
- return processedText;
13252
+ async function compilePipeline(pipelineString, tools, options) {
13253
+ let pipelineJson = parsePipeline(pipelineString);
13254
+ if (tools !== undefined && tools.llm !== undefined) {
13255
+ pipelineJson = await preparePipeline(pipelineJson, tools, options || {
13256
+ rootDirname: null,
13257
+ });
13229
13258
  }
13259
+ // Note: No need to use `$exportJson` because `parsePipeline` and `preparePipeline` already do that
13260
+ return pipelineJson;
13230
13261
  }
13231
13262
  /**
13232
- * TODO: [🧠] Should this also unwrap the (parenthesis)
13263
+ * TODO: [🏏] Leverage the batch API and build queues @see https://platform.openai.com/docs/guides/batch
13264
+ * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
13265
+ * TODO: [🧠] Should be in generated JSON file GENERATOR_WARNING
13233
13266
  */
13234
13267
 
13235
13268
  /**
@@ -19444,7 +19477,7 @@ class OpenAiCompatibleExecutionTools {
19444
19477
  let threadMessages = [];
19445
19478
  if ('thread' in prompt && Array.isArray(prompt.thread)) {
19446
19479
  threadMessages = prompt.thread.map((msg) => ({
19447
- role: msg.role === 'assistant' ? 'assistant' : 'user',
19480
+ role: msg.sender === 'assistant' ? 'assistant' : 'user',
19448
19481
  content: msg.content,
19449
19482
  }));
19450
19483
  }
@@ -19857,13 +19890,14 @@ class OpenAiCompatibleExecutionTools {
19857
19890
  const modelName = currentModelRequirements.modelName || this.getDefaultImageGenerationModel().modelName;
19858
19891
  const modelSettings = {
19859
19892
  model: modelName,
19860
- // size: currentModelRequirements.size,
19861
- // quality: currentModelRequirements.quality,
19862
- // style: currentModelRequirements.style,
19893
+ size: currentModelRequirements.size,
19894
+ quality: currentModelRequirements.quality,
19895
+ style: currentModelRequirements.style,
19863
19896
  };
19864
19897
  const rawPromptContent = templateParameters(content, { ...parameters, modelName });
19865
19898
  const rawRequest = {
19866
19899
  ...modelSettings,
19900
+ size: modelSettings.size || '1024x1024',
19867
19901
  prompt: rawPromptContent,
19868
19902
  user: (_a = this.options.userId) === null || _a === void 0 ? void 0 : _a.toString(),
19869
19903
  response_format: 'url', // TODO: [🧠] Maybe allow b64_json
@@ -20400,10 +20434,10 @@ class OllamaExecutionTools extends OpenAiCompatibleExecutionTools {
20400
20434
  // <- TODO: [🛄]
20401
20435
  }
20402
20436
  /**
20403
- * Default model for image generation variant.
20437
+ * Default model for completion variant.
20404
20438
  */
20405
20439
  getDefaultImageGenerationModel() {
20406
- return this.getDefaultModel('!!!'); // <- TODO: [🧠] Pick the best default model
20440
+ return this.getDefaultModel('dall-e-3');
20407
20441
  // <- TODO: [🛄]
20408
20442
  }
20409
20443
  }
@@ -21218,11 +21252,10 @@ class HardcodedOpenAiCompatibleExecutionTools extends OpenAiCompatibleExecutionT
21218
21252
  throw new PipelineExecutionError(`${this.title} does not support EMBEDDING model variant`);
21219
21253
  }
21220
21254
  /**
21221
- * Default model for image generation variant.
21255
+ * Default model for completion variant.
21222
21256
  */
21223
21257
  getDefaultImageGenerationModel() {
21224
- return this.getDefaultModel('!!!'); // <- TODO: [🧠] Pick the best default model
21225
- // <- TODO: [🛄]
21258
+ throw new PipelineExecutionError(`${this.title} does not support IMAGE_GENERATION model variant`);
21226
21259
  }
21227
21260
  }
21228
21261
  /**
@@ -23098,6 +23131,114 @@ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
23098
23131
  * Note: [💞] Ignore a discrepancy between file name and entity name
23099
23132
  */
23100
23133
 
23134
+ /**
23135
+ * DICTIONARY commitment definition
23136
+ *
23137
+ * The DICTIONARY commitment defines specific terms and their meanings that the agent should use correctly
23138
+ * in its reasoning and responses. This ensures consistent terminology usage.
23139
+ *
23140
+ * Key features:
23141
+ * - Multiple DICTIONARY commitments are automatically merged into one
23142
+ * - Content is placed in a dedicated section of the system message
23143
+ * - Terms and definitions are stored in metadata.DICTIONARY for debugging
23144
+ * - Agent should use the defined terms correctly in responses
23145
+ *
23146
+ * Example usage in agent source:
23147
+ *
23148
+ * ```book
23149
+ * Legal Assistant
23150
+ *
23151
+ * PERSONA You are a knowledgeable legal assistant
23152
+ * DICTIONARY Misdemeanor is a minor wrongdoing or criminal offense
23153
+ * DICTIONARY Felony is a serious crime usually punishable by imprisonment for more than one year
23154
+ * DICTIONARY Tort is a civil wrong that causes harm or loss to another person, leading to legal liability
23155
+ * ```
23156
+ *
23157
+ * @private [🪔] Maybe export the commitments through some package
23158
+ */
23159
+ class DictionaryCommitmentDefinition extends BaseCommitmentDefinition {
23160
+ constructor() {
23161
+ super('DICTIONARY');
23162
+ }
23163
+ /**
23164
+ * Short one-line description of DICTIONARY.
23165
+ */
23166
+ get description() {
23167
+ return 'Define terms and their meanings for consistent terminology usage.';
23168
+ }
23169
+ /**
23170
+ * Icon for this commitment.
23171
+ */
23172
+ get icon() {
23173
+ return '📚';
23174
+ }
23175
+ /**
23176
+ * Markdown documentation for DICTIONARY commitment.
23177
+ */
23178
+ get documentation() {
23179
+ return spaceTrim$1(`
23180
+ # DICTIONARY
23181
+
23182
+ Defines specific terms and their meanings that the agent should use correctly in reasoning and responses.
23183
+
23184
+ ## Key aspects
23185
+
23186
+ - Multiple \`DICTIONARY\` commitments are merged together.
23187
+ - Terms are defined in the format: "Term is definition"
23188
+ - The agent should use these terms consistently in responses.
23189
+ - Definitions help ensure accurate and consistent terminology.
23190
+
23191
+ ## Examples
23192
+
23193
+ \`\`\`book
23194
+ Legal Assistant
23195
+
23196
+ PERSONA You are a knowledgeable legal assistant specializing in criminal law
23197
+ DICTIONARY Misdemeanor is a minor wrongdoing or criminal offense
23198
+ DICTIONARY Felony is a serious crime usually punishable by imprisonment for more than one year
23199
+ DICTIONARY Tort is a civil wrong that causes harm or loss to another person, leading to legal liability
23200
+ \`\`\`
23201
+
23202
+ \`\`\`book
23203
+ Medical Assistant
23204
+
23205
+ PERSONA You are a helpful medical assistant
23206
+ DICTIONARY Hypertension is persistently high blood pressure
23207
+ DICTIONARY Diabetes is a chronic condition that affects how the body processes blood sugar
23208
+ DICTIONARY Vaccine is a biological preparation that provides active immunity to a particular disease
23209
+ \`\`\`
23210
+ `);
23211
+ }
23212
+ applyToAgentModelRequirements(requirements, content) {
23213
+ var _a;
23214
+ const trimmedContent = content.trim();
23215
+ if (!trimmedContent) {
23216
+ return requirements;
23217
+ }
23218
+ // Get existing dictionary entries from metadata
23219
+ const existingDictionary = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.DICTIONARY) || '';
23220
+ // Merge the new dictionary entry with existing entries
23221
+ const mergedDictionary = existingDictionary
23222
+ ? `${existingDictionary}\n${trimmedContent}`
23223
+ : trimmedContent;
23224
+ // Store the merged dictionary in metadata for debugging and inspection
23225
+ const updatedMetadata = {
23226
+ ...requirements.metadata,
23227
+ DICTIONARY: mergedDictionary,
23228
+ };
23229
+ // Create the dictionary section for the system message
23230
+ // Format: "# DICTIONARY\nTerm: definition\nTerm: definition..."
23231
+ const dictionarySection = `# DICTIONARY\n${mergedDictionary}`;
23232
+ return {
23233
+ ...this.appendToSystemMessage(requirements, dictionarySection),
23234
+ metadata: updatedMetadata,
23235
+ };
23236
+ }
23237
+ }
23238
+ /**
23239
+ * Note: [💞] Ignore a discrepancy between file name and entity name
23240
+ */
23241
+
23101
23242
  /**
23102
23243
  * FORMAT commitment definition
23103
23244
  *
@@ -25918,6 +26059,7 @@ const COMMITMENT_REGISTRY = [
25918
26059
  new DeleteCommitmentDefinition('CANCEL'),
25919
26060
  new DeleteCommitmentDefinition('DISCARD'),
25920
26061
  new DeleteCommitmentDefinition('REMOVE'),
26062
+ new DictionaryCommitmentDefinition(),
25921
26063
  new OpenCommitmentDefinition(),
25922
26064
  new ClosedCommitmentDefinition(),
25923
26065
  new UseBrowserCommitmentDefinition(),
@@ -26002,17 +26144,64 @@ function parseAgentSourceWithCommitments(agentSource) {
26002
26144
  };
26003
26145
  }
26004
26146
  const lines = agentSource.split('\n');
26005
- const agentName = (((_a = lines[0]) === null || _a === void 0 ? void 0 : _a.trim()) || null);
26147
+ let agentName = null;
26148
+ let agentNameLineIndex = -1;
26149
+ // Find the agent name: first non-empty line that is not a commitment and not a horizontal line
26150
+ for (let i = 0; i < lines.length; i++) {
26151
+ const line = lines[i];
26152
+ if (line === undefined) {
26153
+ continue;
26154
+ }
26155
+ const trimmed = line.trim();
26156
+ if (!trimmed) {
26157
+ continue;
26158
+ }
26159
+ const isHorizontal = HORIZONTAL_LINE_PATTERN.test(line);
26160
+ if (isHorizontal) {
26161
+ continue;
26162
+ }
26163
+ let isCommitment = false;
26164
+ for (const definition of COMMITMENT_REGISTRY) {
26165
+ const typeRegex = definition.createTypeRegex();
26166
+ const match = typeRegex.exec(trimmed);
26167
+ if (match && ((_a = match.groups) === null || _a === void 0 ? void 0 : _a.type)) {
26168
+ isCommitment = true;
26169
+ break;
26170
+ }
26171
+ }
26172
+ if (!isCommitment) {
26173
+ agentName = trimmed;
26174
+ agentNameLineIndex = i;
26175
+ break;
26176
+ }
26177
+ }
26006
26178
  const commitments = [];
26007
26179
  const nonCommitmentLines = [];
26008
- // Always add the first line (agent name) to non-commitment lines
26009
- if (lines[0] !== undefined) {
26010
- nonCommitmentLines.push(lines[0]);
26180
+ // Add lines before agentName that are horizontal lines (they are non-commitment)
26181
+ for (let i = 0; i < agentNameLineIndex; i++) {
26182
+ const line = lines[i];
26183
+ if (line === undefined) {
26184
+ continue;
26185
+ }
26186
+ const trimmed = line.trim();
26187
+ if (!trimmed) {
26188
+ continue;
26189
+ }
26190
+ const isHorizontal = HORIZONTAL_LINE_PATTERN.test(line);
26191
+ if (isHorizontal) {
26192
+ nonCommitmentLines.push(line);
26193
+ }
26194
+ // Note: Commitments before agentName are not added to nonCommitmentLines
26195
+ }
26196
+ // Add the agent name line to non-commitment lines
26197
+ if (agentNameLineIndex >= 0) {
26198
+ nonCommitmentLines.push(lines[agentNameLineIndex]);
26011
26199
  }
26012
26200
  // Parse commitments with multiline support
26013
26201
  let currentCommitment = null;
26014
- // Process lines starting from the second line (skip agent name)
26015
- for (let i = 1; i < lines.length; i++) {
26202
+ // Process lines starting from after the agent name line
26203
+ const startIndex = agentNameLineIndex >= 0 ? agentNameLineIndex + 1 : 0;
26204
+ for (let i = startIndex; i < lines.length; i++) {
26016
26205
  const line = lines[i];
26017
26206
  if (line === undefined) {
26018
26207
  continue;
@@ -26232,7 +26421,12 @@ async function createAgentModelRequirementsWithCommitments(agentSource, modelNam
26232
26421
  };
26233
26422
  }
26234
26423
  // Apply each commitment in order using reduce-like pattern
26235
- for (const commitment of filteredCommitments) {
26424
+ for (let i = 0; i < filteredCommitments.length; i++) {
26425
+ const commitment = filteredCommitments[i];
26426
+ // CLOSED commitment should work only if its the last commitment in the book
26427
+ if (commitment.type === 'CLOSED' && i !== filteredCommitments.length - 1) {
26428
+ continue;
26429
+ }
26236
26430
  const definition = getCommitmentDefinition(commitment.type);
26237
26431
  if (definition) {
26238
26432
  try {
@@ -26273,44 +26467,6 @@ async function createAgentModelRequirementsWithCommitments(agentSource, modelNam
26273
26467
  };
26274
26468
  }
26275
26469
 
26276
- /**
26277
- * Generates a gravatar URL based on agent name for fallback avatar
26278
- *
26279
- * @param agentName The agent name to generate avatar for
26280
- * @returns Gravatar URL
26281
- *
26282
- * @private - [🤹] The fact that profile image is Gravatar is just implementation detail which should be hidden for consumer
26283
- */
26284
- function generateGravatarUrl(agentName) {
26285
- // Use a default name if none provided
26286
- const safeName = agentName || 'Anonymous Agent';
26287
- // Create a simple hash from the name for consistent avatar
26288
- let hash = 0;
26289
- for (let i = 0; i < safeName.length; i++) {
26290
- const char = safeName.charCodeAt(i);
26291
- hash = (hash << 5) - hash + char;
26292
- hash = hash & hash; // Convert to 32bit integer
26293
- }
26294
- const avatarId = Math.abs(hash).toString();
26295
- return `https://www.gravatar.com/avatar/${avatarId}?default=robohash&size=200&rating=x`;
26296
- }
26297
-
26298
- /**
26299
- * Generates an image for the agent to use as profile image
26300
- *
26301
- * @param agentName The agent name to generate avatar for
26302
- * @returns The placeholder profile image URL for the agent
26303
- *
26304
- * @public exported from `@promptbook/core`
26305
- */
26306
- function generatePlaceholderAgentProfileImageUrl(agentName) {
26307
- // Note: [🤹] The fact that profile image is Gravatar is just implementation detail which should be hidden for consumer
26308
- return generateGravatarUrl(agentName);
26309
- }
26310
- /**
26311
- * TODO: [🤹] Figure out best placeholder image generator https://i.pravatar.cc/1000?u=568
26312
- */
26313
-
26314
26470
  /**
26315
26471
  * Computes SHA-256 hash of the agent source
26316
26472
  *
@@ -26378,7 +26534,57 @@ function parseAgentSource(agentSource) {
26378
26534
  }
26379
26535
  const meta = {};
26380
26536
  const links = [];
26537
+ const capabilities = [];
26381
26538
  for (const commitment of parseResult.commitments) {
26539
+ if (commitment.type === 'USE BROWSER') {
26540
+ capabilities.push({
26541
+ type: 'browser',
26542
+ label: 'Browser',
26543
+ iconName: 'Globe',
26544
+ });
26545
+ continue;
26546
+ }
26547
+ if (commitment.type === 'USE SEARCH ENGINE') {
26548
+ capabilities.push({
26549
+ type: 'search-engine',
26550
+ label: 'Search Internet',
26551
+ iconName: 'Search',
26552
+ });
26553
+ continue;
26554
+ }
26555
+ if (commitment.type === 'KNOWLEDGE') {
26556
+ const content = spaceTrim$2(commitment.content).split('\n')[0] || '';
26557
+ let label = content;
26558
+ let iconName = 'Book';
26559
+ if (content.startsWith('http://') || content.startsWith('https://')) {
26560
+ try {
26561
+ const url = new URL(content);
26562
+ if (url.pathname.endsWith('.pdf')) {
26563
+ label = url.pathname.split('/').pop() || 'Document.pdf';
26564
+ iconName = 'FileText';
26565
+ }
26566
+ else {
26567
+ label = url.hostname.replace(/^www\./, '');
26568
+ }
26569
+ }
26570
+ catch (e) {
26571
+ // Invalid URL, treat as text
26572
+ }
26573
+ }
26574
+ else {
26575
+ // Text content - take first few words
26576
+ const words = content.split(/\s+/);
26577
+ if (words.length > 4) {
26578
+ label = words.slice(0, 4).join(' ') + '...';
26579
+ }
26580
+ }
26581
+ capabilities.push({
26582
+ type: 'knowledge',
26583
+ label,
26584
+ iconName,
26585
+ });
26586
+ continue;
26587
+ }
26382
26588
  if (commitment.type === 'META LINK') {
26383
26589
  const linkValue = spaceTrim$2(commitment.content);
26384
26590
  links.push(linkValue);
@@ -26408,10 +26614,6 @@ function parseAgentSource(agentSource) {
26408
26614
  const metaType = normalizeTo_camelCase(metaTypeRaw);
26409
26615
  meta[metaType] = spaceTrim$2(commitment.content.substring(metaTypeRaw.length));
26410
26616
  }
26411
- // Generate gravatar fallback if no meta image specified
26412
- if (!meta.image) {
26413
- meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
26414
- }
26415
26617
  // Generate fullname fallback if no meta fullname specified
26416
26618
  if (!meta.fullname) {
26417
26619
  meta.fullname = parseResult.agentName || createDefaultAgentName(agentSource);
@@ -26423,11 +26625,13 @@ function parseAgentSource(agentSource) {
26423
26625
  return {
26424
26626
  agentName: normalizeAgentName(parseResult.agentName || createDefaultAgentName(agentSource)),
26425
26627
  agentHash,
26628
+ permanentId: meta.id,
26426
26629
  personaDescription,
26427
26630
  initialMessage,
26428
26631
  meta,
26429
26632
  links,
26430
26633
  parameters,
26634
+ capabilities,
26431
26635
  };
26432
26636
  }
26433
26637
  /**