@promptbook/markdown-utils 0.89.0-8 → 0.89.0

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 (50) hide show
  1. package/README.md +9 -11
  2. package/esm/index.es.js +650 -537
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/typings/servers.d.ts +40 -0
  5. package/esm/typings/src/_packages/core.index.d.ts +12 -4
  6. package/esm/typings/src/_packages/remote-client.index.d.ts +6 -6
  7. package/esm/typings/src/_packages/remote-server.index.d.ts +6 -6
  8. package/esm/typings/src/_packages/types.index.d.ts +24 -14
  9. package/esm/typings/src/_packages/utils.index.d.ts +4 -0
  10. package/esm/typings/src/cli/cli-commands/login.d.ts +0 -1
  11. package/esm/typings/src/cli/common/$provideLlmToolsForCli.d.ts +16 -3
  12. package/esm/typings/src/cli/test/ptbk.d.ts +1 -1
  13. package/esm/typings/src/commands/EXPECT/expectCommandParser.d.ts +2 -0
  14. package/esm/typings/src/config.d.ts +10 -19
  15. package/esm/typings/src/errors/0-index.d.ts +8 -2
  16. package/esm/typings/src/errors/PipelineExecutionError.d.ts +1 -1
  17. package/esm/typings/src/errors/PromptbookFetchError.d.ts +9 -0
  18. package/esm/typings/src/errors/WrappedError.d.ts +10 -0
  19. package/esm/typings/src/errors/assertsError.d.ts +11 -0
  20. package/esm/typings/src/execution/PromptbookFetch.d.ts +1 -1
  21. package/esm/typings/src/formats/csv/utils/isValidCsvString.d.ts +9 -0
  22. package/esm/typings/src/formats/csv/utils/isValidCsvString.test.d.ts +1 -0
  23. package/esm/typings/src/formats/json/utils/isValidJsonString.d.ts +3 -0
  24. package/esm/typings/src/formats/xml/utils/isValidXmlString.d.ts +9 -0
  25. package/esm/typings/src/formats/xml/utils/isValidXmlString.test.d.ts +1 -0
  26. package/esm/typings/src/llm-providers/_common/register/$provideEnvFilename.d.ts +12 -0
  27. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +2 -8
  28. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForTestingAndScriptsAndPlayground.d.ts +2 -0
  29. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizzardOrCli.d.ts +15 -4
  30. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -0
  31. package/esm/typings/src/remote-server/openapi-types.d.ts +284 -0
  32. package/esm/typings/src/remote-server/openapi.d.ts +187 -0
  33. package/esm/typings/src/remote-server/socket-types/_subtypes/{PromptbookServer_Identification.d.ts → Identification.d.ts} +9 -3
  34. package/esm/typings/src/remote-server/socket-types/_subtypes/identificationToPromptbookToken.d.ts +11 -0
  35. package/esm/typings/src/remote-server/socket-types/_subtypes/promptbookTokenToIdentification.d.ts +10 -0
  36. package/esm/typings/src/remote-server/socket-types/listModels/PromptbookServer_ListModels_Request.d.ts +2 -2
  37. package/esm/typings/src/remote-server/socket-types/prepare/PromptbookServer_PreparePipeline_Request.d.ts +2 -2
  38. package/esm/typings/src/remote-server/socket-types/prompt/PromptbookServer_Prompt_Request.d.ts +2 -2
  39. package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -2
  40. package/esm/typings/src/remote-server/types/RemoteClientOptions.d.ts +2 -2
  41. package/esm/typings/src/remote-server/types/RemoteServerOptions.d.ts +57 -38
  42. package/esm/typings/src/scrapers/_common/utils/{scraperFetch.d.ts → promptbookFetch.d.ts} +2 -2
  43. package/esm/typings/src/storage/env-storage/$EnvStorage.d.ts +40 -0
  44. package/esm/typings/src/types/typeAliases.d.ts +26 -0
  45. package/package.json +7 -3
  46. package/umd/index.umd.js +650 -537
  47. package/umd/index.umd.js.map +1 -1
  48. package/esm/typings/src/cli/test/ptbk2.d.ts +0 -5
  49. package/esm/typings/src/playground/BrjappConnector.d.ts +0 -67
  50. package/esm/typings/src/playground/brjapp-api-schema.d.ts +0 -12879
package/umd/index.umd.js CHANGED
@@ -25,7 +25,7 @@
25
25
  * @generated
26
26
  * @see https://github.com/webgptorg/promptbook
27
27
  */
28
- const PROMPTBOOK_ENGINE_VERSION = '0.89.0-8';
28
+ const PROMPTBOOK_ENGINE_VERSION = '0.89.0';
29
29
  /**
30
30
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
31
31
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -173,547 +173,666 @@
173
173
  }
174
174
 
175
175
  /**
176
- * Function isValidJsonString will tell you if the string is valid JSON or not
176
+ * Returns the same value that is passed as argument.
177
+ * No side effects.
177
178
  *
178
- * @public exported from `@promptbook/utils`
179
+ * Note: It can be usefull for:
180
+ *
181
+ * 1) Leveling indentation
182
+ * 2) Putting always-true or always-false conditions without getting eslint errors
183
+ *
184
+ * @param value any values
185
+ * @returns the same values
186
+ * @private within the repository
179
187
  */
180
- function isValidJsonString(value /* <- [👨‍⚖️] */) {
181
- try {
182
- JSON.parse(value);
183
- return true;
184
- }
185
- catch (error) {
186
- if (!(error instanceof Error)) {
187
- throw error;
188
- }
189
- if (error.message.includes('Unexpected token')) {
190
- return false;
191
- }
192
- return false;
188
+ function just(value) {
189
+ if (value === undefined) {
190
+ return undefined;
193
191
  }
192
+ return value;
194
193
  }
195
194
 
196
195
  /**
197
- * Extracts extracts exactly one valid JSON code block
196
+ * Warning message for the generated sections and files files
198
197
  *
199
- * - When given string is a valid JSON as it is, it just returns it
200
- * - When there is no JSON code block the function throws a `ParseError`
201
- * - When there are multiple JSON code blocks the function throws a `ParseError`
198
+ * @private within the repository
199
+ */
200
+ const GENERATOR_WARNING = `⚠️ WARNING: This code has been generated so that any manual changes will be overwritten`;
201
+ /**
202
+ * Name for the Promptbook
202
203
  *
203
- * Note: It is not important if marked as ```json BUT if it is VALID JSON
204
- * Note: There are multiple simmilar function:
205
- * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
206
- * - `extractJsonBlock` extracts exactly one valid JSON code block
207
- * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
208
- * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
204
+ * TODO: [🗽] Unite branding and make single place for it
209
205
  *
210
- * @public exported from `@promptbook/markdown-utils`
211
- * @throws {ParseError} if there is no valid JSON block in the markdown
206
+ * @public exported from `@promptbook/core`
212
207
  */
213
- function extractJsonBlock(markdown) {
214
- if (isValidJsonString(markdown)) {
215
- return markdown;
216
- }
217
- const codeBlocks = extractAllBlocksFromMarkdown(markdown);
218
- const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
219
- if (jsonBlocks.length === 0) {
220
- throw new Error('There is no valid JSON block in the markdown');
221
- }
222
- if (jsonBlocks.length > 1) {
223
- throw new Error('There are multiple JSON code blocks in the markdown');
224
- }
225
- return jsonBlocks[0].content;
226
- }
208
+ const NAME = `Promptbook`;
227
209
  /**
228
- * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
229
- * TODO: [🏢] Make this logic part of `JsonFormatDefinition` or `isValidJsonString`
210
+ * Email of the responsible person
211
+ *
212
+ * @public exported from `@promptbook/core`
230
213
  */
231
-
214
+ const ADMIN_EMAIL = 'pavol@ptbk.io';
232
215
  /**
233
- * Just says that the variable is not used but should be kept
234
- * No side effects.
216
+ * Name of the responsible person for the Promptbook on GitHub
235
217
  *
236
- * Note: It can be usefull for:
218
+ * @public exported from `@promptbook/core`
219
+ */
220
+ const ADMIN_GITHUB_NAME = 'hejny';
221
+ // <- TODO: [🐊] Pick the best claim
222
+ /**
223
+ * When the title is not provided, the default title is used
237
224
  *
238
- * 1) Suppressing eager optimization of unused imports
239
- * 2) Suppressing eslint errors of unused variables in the tests
240
- * 3) Keeping the type of the variable for type testing
225
+ * @public exported from `@promptbook/core`
226
+ */
227
+ const DEFAULT_BOOK_TITLE = `✨ Untitled Book`;
228
+ /**
229
+ * Maximum file size limit
241
230
  *
242
- * @param value any values
243
- * @returns void
244
- * @private within the repository
231
+ * @public exported from `@promptbook/core`
245
232
  */
246
- function keepUnused(...valuesToKeep) {
247
- }
248
-
249
- var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModelNames",description:"List of available model names separated by comma (,)",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n```json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n```\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelRequirements",format:"JSON",dependentParameterNames:["availableModelNames","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModelNames}` List of available model names separated by comma (,)\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n\\`\\`\\`json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
250
-
233
+ const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
234
+ // <- TODO: [🧠] Better system for generator warnings - not always "code" and "by `@promptbook/cli`"
251
235
  /**
252
- * Checks if value is valid email
236
+ * The maximum number of iterations for a loops
237
+ *
238
+ * @private within the repository - too low-level in comparison with other `MAX_...`
239
+ */
240
+ const LOOP_LIMIT = 1000;
241
+ /**
242
+ * Strings to represent various values in the context of parameter values
253
243
  *
254
244
  * @public exported from `@promptbook/utils`
255
245
  */
256
- function isValidEmail(email) {
257
- if (typeof email !== 'string') {
258
- return false;
259
- }
260
- if (email.split('\n').length > 1) {
261
- return false;
262
- }
263
- return /^.+@.+\..+$/.test(email);
264
- }
265
-
246
+ const VALUE_STRINGS = {
247
+ empty: '(nothing; empty string)',
248
+ null: '(no value; null)',
249
+ undefined: '(unknown value; undefined)',
250
+ nan: '(not a number; NaN)',
251
+ infinity: '(infinity; ∞)',
252
+ negativeInfinity: '(negative infinity; -∞)',
253
+ unserializable: '(unserializable value)',
254
+ circular: '(circular JSON)',
255
+ };
266
256
  /**
267
- * Tests if given string is valid URL.
257
+ * Small number limit
268
258
  *
269
- * Note: This does not check if the file exists only if the path is valid
270
259
  * @public exported from `@promptbook/utils`
271
260
  */
272
- function isValidFilePath(filename) {
273
- if (typeof filename !== 'string') {
274
- return false;
275
- }
276
- if (filename.split('\n').length > 1) {
277
- return false;
278
- }
279
- if (filename.split(' ').length >
280
- 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
281
- return false;
282
- }
283
- const filenameSlashes = filename.split('\\').join('/');
284
- // Absolute Unix path: /hello.txt
285
- if (/^(\/)/i.test(filenameSlashes)) {
286
- // console.log(filename, 'Absolute Unix path: /hello.txt');
287
- return true;
288
- }
289
- // Absolute Windows path: /hello.txt
290
- if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
291
- // console.log(filename, 'Absolute Windows path: /hello.txt');
292
- return true;
293
- }
294
- // Relative path: ./hello.txt
295
- if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
296
- // console.log(filename, 'Relative path: ./hello.txt');
297
- return true;
298
- }
299
- // Allow paths like foo/hello
300
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
301
- // console.log(filename, 'Allow paths like foo/hello');
302
- return true;
303
- }
304
- // Allow paths like hello.book
305
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
306
- // console.log(filename, 'Allow paths like hello.book');
307
- return true;
308
- }
309
- return false;
310
- }
261
+ const SMALL_NUMBER = 0.001;
311
262
  /**
312
- * TODO: [🍏] Implement for MacOs
263
+ * Short time interval to prevent race conditions in milliseconds
264
+ *
265
+ * @private within the repository - too low-level in comparison with other `MAX_...`
313
266
  */
314
-
267
+ const IMMEDIATE_TIME = 10;
315
268
  /**
316
- * Tests if given string is valid URL.
269
+ * The maximum length of the (generated) filename
317
270
  *
318
- * Note: Dataurl are considered perfectly valid.
319
- * Note: There are two simmilar functions:
320
- * - `isValidUrl` which tests any URL
321
- * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
271
+ * @public exported from `@promptbook/core`
272
+ */
273
+ const MAX_FILENAME_LENGTH = 30;
274
+ /**
275
+ * Strategy for caching the intermediate results for knowledge sources
322
276
  *
323
- * @public exported from `@promptbook/utils`
277
+ * @public exported from `@promptbook/core`
324
278
  */
325
- function isValidUrl(url) {
326
- if (typeof url !== 'string') {
327
- return false;
328
- }
329
- try {
330
- if (url.startsWith('blob:')) {
331
- url = url.replace(/^blob:/, '');
332
- }
333
- const urlObject = new URL(url /* because fail is handled */);
334
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
335
- return false;
336
- }
337
- return true;
338
- }
339
- catch (error) {
340
- return false;
341
- }
342
- }
343
-
279
+ const DEFAULT_INTERMEDIATE_FILES_STRATEGY = 'HIDE_AND_KEEP';
280
+ // <- TODO: [😡] Change to 'VISIBLE'
344
281
  /**
345
- * Function `validatePipelineString` will validate the if the string is a valid pipeline string
346
- * It does not check if the string is fully logically correct, but if it is a string that can be a pipeline string or the string looks completely different.
282
+ * The maximum number of (LLM) tasks running in parallel
347
283
  *
348
- * @param {string} pipelineString the candidate for a pipeline string
349
- * @returns {PipelineString} the same string as input, but validated as valid
350
- * @throws {ParseError} if the string is not a valid pipeline string
351
284
  * @public exported from `@promptbook/core`
352
285
  */
353
- function validatePipelineString(pipelineString) {
354
- if (isValidJsonString(pipelineString)) {
355
- throw new ParseError('Expected a book, but got a JSON string');
356
- }
357
- else if (isValidUrl(pipelineString)) {
358
- throw new ParseError(`Expected a book, but got just the URL "${pipelineString}"`);
359
- }
360
- else if (isValidFilePath(pipelineString)) {
361
- throw new ParseError(`Expected a book, but got just the file path "${pipelineString}"`);
362
- }
363
- else if (isValidEmail(pipelineString)) {
364
- throw new ParseError(`Expected a book, but got just the email "${pipelineString}"`);
365
- }
366
- // <- TODO: Implement the validation + add tests when the pipeline logic considered as invalid
367
- return pipelineString;
368
- }
286
+ const DEFAULT_MAX_PARALLEL_COUNT = 5; // <- TODO: [🤹‍♂️]
369
287
  /**
370
- * TODO: [🧠][🈴] Where is the best location for this file
288
+ * The maximum number of attempts to execute LLM task before giving up
289
+ *
290
+ * @public exported from `@promptbook/core`
371
291
  */
372
-
292
+ const DEFAULT_MAX_EXECUTION_ATTEMPTS = 10; // <- TODO: [🤹‍♂️]
293
+ // <- TODO: [🕝] Make also `BOOKS_DIRNAME_ALTERNATIVES`
294
+ // TODO: Just `.promptbook` in config, hardcode subfolders like `download-cache` or `execution-cache`
373
295
  /**
374
- * Prettify the html code
296
+ * Where to store the temporary downloads
375
297
  *
376
- * @param content raw html code
377
- * @returns formatted html code
378
- * @private withing the package because of HUGE size of prettier dependency
298
+ * Note: When the folder does not exist, it is created recursively
299
+ *
300
+ * @public exported from `@promptbook/core`
379
301
  */
380
- function prettifyMarkdown(content) {
381
- try {
382
- return prettier.format(content, {
383
- parser: 'markdown',
384
- plugins: [parserHtml__default["default"]],
385
- // TODO: DRY - make some import or auto-copy of .prettierrc
386
- endOfLine: 'lf',
387
- tabWidth: 4,
388
- singleQuote: true,
389
- trailingComma: 'all',
390
- arrowParens: 'always',
391
- printWidth: 120,
392
- htmlWhitespaceSensitivity: 'ignore',
393
- jsxBracketSameLine: false,
394
- bracketSpacing: true,
395
- });
396
- }
397
- catch (error) {
398
- // TODO: [🟥] Detect browser / node and make it colorfull
399
- console.error('There was an error with prettifying the markdown, using the original as the fallback', {
400
- error,
401
- html: content,
402
- });
403
- return content;
404
- }
405
- }
406
-
302
+ const DEFAULT_DOWNLOAD_CACHE_DIRNAME = './.promptbook/download-cache';
407
303
  /**
408
- * Converts promptbook in JSON format to string format
304
+ * Where to store the scrape cache
305
+ *
306
+ * Note: When the folder does not exist, it is created recursively
409
307
  *
410
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
411
- * @param pipelineJson Promptbook in JSON format (.bookc)
412
- * @returns Promptbook in string format (.book.md)
413
308
  * @public exported from `@promptbook/core`
414
309
  */
415
- function pipelineJsonToString(pipelineJson) {
416
- const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
417
- let pipelineString = `# ${title}`;
418
- if (description) {
419
- pipelineString += '\n\n';
420
- pipelineString += description;
421
- }
422
- const commands = [];
423
- if (pipelineUrl) {
424
- commands.push(`PIPELINE URL ${pipelineUrl}`);
425
- }
426
- if (bookVersion !== `undefined`) {
427
- commands.push(`BOOK VERSION ${bookVersion}`);
428
- }
429
- // TODO: [main] !!5 This increases size of the bundle and is probbably not necessary
430
- pipelineString = prettifyMarkdown(pipelineString);
431
- for (const parameter of parameters.filter(({ isInput }) => isInput)) {
432
- commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
433
- }
434
- for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
435
- commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
436
- }
437
- pipelineString += '\n\n';
438
- pipelineString += commands.map((command) => `- ${command}`).join('\n');
439
- for (const task of tasks) {
440
- const {
441
- /* Note: Not using:> name, */
442
- title, description,
443
- /* Note: dependentParameterNames, */
444
- jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
445
- pipelineString += '\n\n';
446
- pipelineString += `## ${title}`;
447
- if (description) {
448
- pipelineString += '\n\n';
449
- pipelineString += description;
450
- }
451
- const commands = [];
452
- let contentLanguage = 'text';
453
- if (taskType === 'PROMPT_TASK') {
454
- const { modelRequirements } = task;
455
- const { modelName, modelVariant } = modelRequirements || {};
456
- // Note: Do nothing, it is default
457
- // commands.push(`PROMPT`);
458
- if (modelVariant) {
459
- commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
460
- }
461
- if (modelName) {
462
- commands.push(`MODEL NAME \`${modelName}\``);
463
- }
464
- }
465
- else if (taskType === 'SIMPLE_TASK') {
466
- commands.push(`SIMPLE TEMPLATE`);
467
- // Note: Nothing special here
468
- }
469
- else if (taskType === 'SCRIPT_TASK') {
470
- commands.push(`SCRIPT`);
471
- if (task.contentLanguage) {
472
- contentLanguage = task.contentLanguage;
473
- }
474
- else {
475
- contentLanguage = '';
476
- }
477
- }
478
- else if (taskType === 'DIALOG_TASK') {
479
- commands.push(`DIALOG`);
480
- // Note: Nothing special here
481
- } // <- }else if([🅱]
482
- if (jokers) {
483
- for (const joker of jokers) {
484
- commands.push(`JOKER {${joker}}`);
485
- }
486
- } /* not else */
487
- if (postprocessing) {
488
- for (const postprocessingFunctionName of postprocessing) {
489
- commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
490
- }
491
- } /* not else */
492
- if (expectations) {
493
- for (const [unit, { min, max }] of Object.entries(expectations)) {
494
- if (min === max) {
495
- commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
496
- }
497
- else {
498
- if (min !== undefined) {
499
- commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
500
- } /* not else */
501
- if (max !== undefined) {
502
- commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
503
- }
504
- }
505
- }
506
- } /* not else */
507
- if (format) {
508
- if (format === 'JSON') {
509
- // TODO: @deprecated remove
510
- commands.push(`FORMAT JSON`);
511
- }
512
- } /* not else */
513
- pipelineString += '\n\n';
514
- pipelineString += commands.map((command) => `- ${command}`).join('\n');
515
- pipelineString += '\n\n';
516
- pipelineString += '```' + contentLanguage;
517
- pipelineString += '\n';
518
- pipelineString += spaceTrim__default["default"](content);
519
- // <- TODO: [main] !!3 Escape
520
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
521
- pipelineString += '\n';
522
- pipelineString += '```';
523
- pipelineString += '\n\n';
524
- pipelineString += `\`-> {${resultingParameterName}}\``; // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
525
- }
526
- return validatePipelineString(pipelineString);
527
- }
528
- /**
529
- * @private internal utility of `pipelineJsonToString`
530
- */
531
- function taskParameterJsonToString(taskParameterJson) {
532
- const { name, description } = taskParameterJson;
533
- let parameterString = `{${name}}`;
534
- if (description) {
535
- parameterString = `${parameterString} ${description}`;
536
- }
537
- return parameterString;
538
- }
539
- /**
540
- * TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
541
- * TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
542
- * TODO: [🏛] Maybe make some markdown builder
543
- * TODO: [🏛] Escape all
544
- * TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
545
- */
546
-
310
+ const DEFAULT_SCRAPE_CACHE_DIRNAME = './.promptbook/scrape-cache';
311
+ // <- TODO: [🧜‍♂️]
547
312
  /**
548
- * Returns the same value that is passed as argument.
549
- * No side effects.
550
- *
551
- * Note: It can be usefull for:
552
- *
553
- * 1) Leveling indentation
554
- * 2) Putting always-true or always-false conditions without getting eslint errors
313
+ * @@@
555
314
  *
556
- * @param value any values
557
- * @returns the same values
558
- * @private within the repository
315
+ * @public exported from `@promptbook/core`
559
316
  */
560
- function just(value) {
561
- if (value === undefined) {
562
- return undefined;
563
- }
564
- return value;
565
- }
566
-
317
+ const DEFAULT_CSV_SETTINGS = Object.freeze({
318
+ delimiter: ',',
319
+ quoteChar: '"',
320
+ newline: '\n',
321
+ skipEmptyLines: true,
322
+ });
567
323
  /**
568
- * Warning message for the generated sections and files files
324
+ * @@@
569
325
  *
570
- * @private within the repository
326
+ * @public exported from `@promptbook/core`
571
327
  */
572
- const GENERATOR_WARNING = `⚠️ WARNING: This code has been generated so that any manual changes will be overwritten`;
328
+ let DEFAULT_IS_VERBOSE = false;
573
329
  /**
574
- * Name for the Promptbook
575
- *
576
- * TODO: [🗽] Unite branding and make single place for it
330
+ * @@@
577
331
  *
578
332
  * @public exported from `@promptbook/core`
579
333
  */
580
- const NAME = `Promptbook`;
334
+ const DEFAULT_IS_AUTO_INSTALLED = false;
581
335
  /**
582
- * Email of the responsible person
336
+ * @@@
583
337
  *
584
- * @public exported from `@promptbook/core`
338
+ * @private within the repository
585
339
  */
586
- const ADMIN_EMAIL = 'pavol@ptbk.io';
340
+ const IS_PIPELINE_LOGIC_VALIDATED = just(
341
+ /**/
342
+ // Note: In normal situations, we check the pipeline logic:
343
+ true);
587
344
  /**
588
- * Name of the responsible person for the Promptbook on GitHub
589
- *
590
- * @public exported from `@promptbook/core`
345
+ * Note: [💞] Ignore a discrepancy between file name and entity name
346
+ * TODO: [🧠][🧜‍♂️] Maybe join remoteServerUrl and path into single value
591
347
  */
592
- const ADMIN_GITHUB_NAME = 'hejny';
348
+
593
349
  /**
594
- * When the title is not provided, the default title is used
350
+ * Make error report URL for the given error
595
351
  *
596
- * @public exported from `@promptbook/core`
352
+ * @private private within the repository
597
353
  */
598
- const DEFAULT_BOOK_TITLE = `✨ Untitled Book`;
354
+ function getErrorReportUrl(error) {
355
+ const report = {
356
+ title: `🐜 Error report from ${NAME}`,
357
+ body: spaceTrim__default["default"]((block) => `
358
+
359
+
360
+ \`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
361
+
362
+ \`\`\`
363
+ ${block(error.message || '(no error message)')}
364
+ \`\`\`
365
+
366
+
367
+ ## More info:
368
+
369
+ - **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
370
+ - **Book language version:** ${BOOK_LANGUAGE_VERSION}
371
+ - **Time:** ${new Date().toISOString()}
372
+
373
+ <details>
374
+ <summary>Stack trace:</summary>
375
+
376
+ ## Stack trace:
377
+
378
+ \`\`\`stacktrace
379
+ ${block(error.stack || '(empty)')}
380
+ \`\`\`
381
+ </details>
382
+
383
+ `),
384
+ };
385
+ const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
386
+ reportUrl.searchParams.set('labels', 'bug');
387
+ reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
388
+ reportUrl.searchParams.set('title', report.title);
389
+ reportUrl.searchParams.set('body', report.body);
390
+ return reportUrl;
391
+ }
392
+
599
393
  /**
600
- * Maximum file size limit
394
+ * This error type indicates that the error should not happen and its last check before crashing with some other error
601
395
  *
602
396
  * @public exported from `@promptbook/core`
603
397
  */
604
- const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
605
- // <- TODO: [🧠] Better system for generator warnings - not always "code" and "by `@promptbook/cli`"
398
+ class UnexpectedError extends Error {
399
+ constructor(message) {
400
+ super(spaceTrim.spaceTrim((block) => `
401
+ ${block(message)}
402
+
403
+ Note: This error should not happen.
404
+ It's probbably a bug in the pipeline collection
405
+
406
+ Please report issue:
407
+ ${block(getErrorReportUrl(new Error(message)).href)}
408
+
409
+ Or contact us on ${ADMIN_EMAIL}
410
+
411
+ `));
412
+ this.name = 'UnexpectedError';
413
+ Object.setPrototypeOf(this, UnexpectedError.prototype);
414
+ }
415
+ }
416
+
606
417
  /**
607
- * The maximum number of iterations for a loops
418
+ * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
608
419
  *
609
- * @private within the repository - too low-level in comparison with other `MAX_...`
420
+ * @public exported from `@promptbook/core`
610
421
  */
611
- const LOOP_LIMIT = 1000;
422
+ class WrappedError extends Error {
423
+ constructor(whatWasThrown) {
424
+ const tag = `[🤮]`;
425
+ console.error(tag, whatWasThrown);
426
+ super(spaceTrim.spaceTrim(`
427
+ Non-Error object was thrown
428
+
429
+ Note: Look for ${tag} in the console for more details
430
+ Please report issue on ${ADMIN_EMAIL}
431
+ `));
432
+ this.name = 'WrappedError';
433
+ Object.setPrototypeOf(this, WrappedError.prototype);
434
+ }
435
+ }
436
+
612
437
  /**
613
- * Strings to represent various values in the context of parameter values
438
+ * Helper used in catch blocks to assert that the error is an instance of `Error`
614
439
  *
615
- * @public exported from `@promptbook/utils`
440
+ * @param whatWasThrown Any object that was thrown
441
+ * @returns Nothing if the error is an instance of `Error`
442
+ * @throws `WrappedError` or `UnexpectedError` if the error is not standard
443
+ *
444
+ * @private within the repository
616
445
  */
617
- const VALUE_STRINGS = {
618
- empty: '(nothing; empty string)',
619
- null: '(no value; null)',
620
- undefined: '(unknown value; undefined)',
621
- nan: '(not a number; NaN)',
622
- infinity: '(infinity; ∞)',
623
- negativeInfinity: '(negative infinity; -∞)',
624
- unserializable: '(unserializable value)',
625
- };
446
+ function assertsError(whatWasThrown) {
447
+ // Case 1: Handle error which was rethrown as `WrappedError`
448
+ if (whatWasThrown instanceof WrappedError) {
449
+ const wrappedError = whatWasThrown;
450
+ throw wrappedError;
451
+ }
452
+ // Case 2: Handle unexpected errors
453
+ if (whatWasThrown instanceof UnexpectedError) {
454
+ const unexpectedError = whatWasThrown;
455
+ throw unexpectedError;
456
+ }
457
+ // Case 3: Handle standard errors - keep them up to consumer
458
+ if (whatWasThrown instanceof Error) {
459
+ return;
460
+ }
461
+ // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
462
+ throw new WrappedError(whatWasThrown);
463
+ }
464
+
626
465
  /**
627
- * Small number limit
466
+ * Function isValidJsonString will tell you if the string is valid JSON or not
467
+ *
468
+ * @param value The string to check
469
+ * @returns True if the string is a valid JSON string, false otherwise
628
470
  *
629
471
  * @public exported from `@promptbook/utils`
630
472
  */
631
- const SMALL_NUMBER = 0.001;
473
+ function isValidJsonString(value /* <- [👨‍⚖️] */) {
474
+ try {
475
+ JSON.parse(value);
476
+ return true;
477
+ }
478
+ catch (error) {
479
+ assertsError(error);
480
+ if (error.message.includes('Unexpected token')) {
481
+ return false;
482
+ }
483
+ return false;
484
+ }
485
+ }
486
+
632
487
  /**
633
- * Short time interval to prevent race conditions in milliseconds
488
+ * Extracts extracts exactly one valid JSON code block
634
489
  *
635
- * @private within the repository - too low-level in comparison with other `MAX_...`
490
+ * - When given string is a valid JSON as it is, it just returns it
491
+ * - When there is no JSON code block the function throws a `ParseError`
492
+ * - When there are multiple JSON code blocks the function throws a `ParseError`
493
+ *
494
+ * Note: It is not important if marked as ```json BUT if it is VALID JSON
495
+ * Note: There are multiple simmilar function:
496
+ * - `extractBlock` just extracts the content of the code block which is also used as build-in function for postprocessing
497
+ * - `extractJsonBlock` extracts exactly one valid JSON code block
498
+ * - `extractOneBlockFromMarkdown` extracts exactly one code block with language of the code block
499
+ * - `extractAllBlocksFromMarkdown` extracts all code blocks with language of the code block
500
+ *
501
+ * @public exported from `@promptbook/markdown-utils`
502
+ * @throws {ParseError} if there is no valid JSON block in the markdown
636
503
  */
637
- const IMMEDIATE_TIME = 10;
504
+ function extractJsonBlock(markdown) {
505
+ if (isValidJsonString(markdown)) {
506
+ return markdown;
507
+ }
508
+ const codeBlocks = extractAllBlocksFromMarkdown(markdown);
509
+ const jsonBlocks = codeBlocks.filter(({ content }) => isValidJsonString(content));
510
+ if (jsonBlocks.length === 0) {
511
+ throw new Error('There is no valid JSON block in the markdown');
512
+ }
513
+ if (jsonBlocks.length > 1) {
514
+ throw new Error('There are multiple JSON code blocks in the markdown');
515
+ }
516
+ return jsonBlocks[0].content;
517
+ }
638
518
  /**
639
- * The maximum length of the (generated) filename
640
- *
641
- * @public exported from `@promptbook/core`
519
+ * TODO: Add some auto-healing logic + extract YAML, JSON5, TOML, etc.
520
+ * TODO: [🏢] Make this logic part of `JsonFormatDefinition` or `isValidJsonString`
642
521
  */
643
- const MAX_FILENAME_LENGTH = 30;
522
+
644
523
  /**
645
- * Strategy for caching the intermediate results for knowledge sources
524
+ * Just says that the variable is not used but should be kept
525
+ * No side effects.
646
526
  *
647
- * @public exported from `@promptbook/core`
527
+ * Note: It can be usefull for:
528
+ *
529
+ * 1) Suppressing eager optimization of unused imports
530
+ * 2) Suppressing eslint errors of unused variables in the tests
531
+ * 3) Keeping the type of the variable for type testing
532
+ *
533
+ * @param value any values
534
+ * @returns void
535
+ * @private within the repository
648
536
  */
649
- const DEFAULT_INTERMEDIATE_FILES_STRATEGY = 'HIDE_AND_KEEP';
650
- // <- TODO: [😡] Change to 'VISIBLE'
537
+ function keepUnused(...valuesToKeep) {
538
+ }
539
+
540
+ var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModelNames",description:"List of available model names separated by comma (,)",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n```json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n```\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelRequirements",format:"JSON",dependentParameterNames:["availableModelNames","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModelNames}` List of available model names separated by comma (,)\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n\\`\\`\\`json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
541
+
651
542
  /**
652
- * The maximum number of (LLM) tasks running in parallel
543
+ * Checks if value is valid email
653
544
  *
654
- * @public exported from `@promptbook/core`
545
+ * @public exported from `@promptbook/utils`
655
546
  */
656
- const DEFAULT_MAX_PARALLEL_COUNT = 5; // <- TODO: [🤹‍♂️]
547
+ function isValidEmail(email) {
548
+ if (typeof email !== 'string') {
549
+ return false;
550
+ }
551
+ if (email.split('\n').length > 1) {
552
+ return false;
553
+ }
554
+ return /^.+@.+\..+$/.test(email);
555
+ }
556
+
657
557
  /**
658
- * The maximum number of attempts to execute LLM task before giving up
558
+ * Tests if given string is valid URL.
659
559
  *
660
- * @public exported from `@promptbook/core`
560
+ * Note: This does not check if the file exists only if the path is valid
561
+ * @public exported from `@promptbook/utils`
661
562
  */
662
- const DEFAULT_MAX_EXECUTION_ATTEMPTS = 10; // <- TODO: [🤹‍♂️]
663
- // <- TODO: [🕝] Make also `BOOKS_DIRNAME_ALTERNATIVES`
664
- // TODO: !!!!!! Just .promptbook dir, hardocode others
563
+ function isValidFilePath(filename) {
564
+ if (typeof filename !== 'string') {
565
+ return false;
566
+ }
567
+ if (filename.split('\n').length > 1) {
568
+ return false;
569
+ }
570
+ if (filename.split(' ').length >
571
+ 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
572
+ return false;
573
+ }
574
+ const filenameSlashes = filename.split('\\').join('/');
575
+ // Absolute Unix path: /hello.txt
576
+ if (/^(\/)/i.test(filenameSlashes)) {
577
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
578
+ return true;
579
+ }
580
+ // Absolute Windows path: /hello.txt
581
+ if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
582
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
583
+ return true;
584
+ }
585
+ // Relative path: ./hello.txt
586
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
587
+ // console.log(filename, 'Relative path: ./hello.txt');
588
+ return true;
589
+ }
590
+ // Allow paths like foo/hello
591
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
592
+ // console.log(filename, 'Allow paths like foo/hello');
593
+ return true;
594
+ }
595
+ // Allow paths like hello.book
596
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
597
+ // console.log(filename, 'Allow paths like hello.book');
598
+ return true;
599
+ }
600
+ return false;
601
+ }
665
602
  /**
666
- * Where to store the temporary downloads
667
- *
668
- * Note: When the folder does not exist, it is created recursively
669
- *
670
- * @public exported from `@promptbook/core`
603
+ * TODO: [🍏] Implement for MacOs
671
604
  */
672
- const DEFAULT_DOWNLOAD_CACHE_DIRNAME = './.promptbook/download-cache';
605
+
673
606
  /**
674
- * Where to store the scrape cache
607
+ * Tests if given string is valid URL.
675
608
  *
676
- * Note: When the folder does not exist, it is created recursively
609
+ * Note: Dataurl are considered perfectly valid.
610
+ * Note: There are two simmilar functions:
611
+ * - `isValidUrl` which tests any URL
612
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
677
613
  *
678
- * @public exported from `@promptbook/core`
614
+ * @public exported from `@promptbook/utils`
679
615
  */
680
- const DEFAULT_SCRAPE_CACHE_DIRNAME = './.promptbook/scrape-cache';
681
- // <- TODO: [🧜‍♂️]
616
+ function isValidUrl(url) {
617
+ if (typeof url !== 'string') {
618
+ return false;
619
+ }
620
+ try {
621
+ if (url.startsWith('blob:')) {
622
+ url = url.replace(/^blob:/, '');
623
+ }
624
+ const urlObject = new URL(url /* because fail is handled */);
625
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
626
+ return false;
627
+ }
628
+ return true;
629
+ }
630
+ catch (error) {
631
+ return false;
632
+ }
633
+ }
634
+
682
635
  /**
683
- * @@@
636
+ * Function `validatePipelineString` will validate the if the string is a valid pipeline string
637
+ * It does not check if the string is fully logically correct, but if it is a string that can be a pipeline string or the string looks completely different.
684
638
  *
639
+ * @param {string} pipelineString the candidate for a pipeline string
640
+ * @returns {PipelineString} the same string as input, but validated as valid
641
+ * @throws {ParseError} if the string is not a valid pipeline string
685
642
  * @public exported from `@promptbook/core`
686
643
  */
687
- const DEFAULT_CSV_SETTINGS = Object.freeze({
688
- delimiter: ',',
689
- quoteChar: '"',
690
- newline: '\n',
691
- skipEmptyLines: true,
692
- });
644
+ function validatePipelineString(pipelineString) {
645
+ if (isValidJsonString(pipelineString)) {
646
+ throw new ParseError('Expected a book, but got a JSON string');
647
+ }
648
+ else if (isValidUrl(pipelineString)) {
649
+ throw new ParseError(`Expected a book, but got just the URL "${pipelineString}"`);
650
+ }
651
+ else if (isValidFilePath(pipelineString)) {
652
+ throw new ParseError(`Expected a book, but got just the file path "${pipelineString}"`);
653
+ }
654
+ else if (isValidEmail(pipelineString)) {
655
+ throw new ParseError(`Expected a book, but got just the email "${pipelineString}"`);
656
+ }
657
+ // <- TODO: Implement the validation + add tests when the pipeline logic considered as invalid
658
+ return pipelineString;
659
+ }
693
660
  /**
694
- * @@@
661
+ * TODO: [🧠][🈴] Where is the best location for this file
662
+ */
663
+
664
+ /**
665
+ * Prettify the html code
695
666
  *
696
- * @public exported from `@promptbook/core`
667
+ * @param content raw html code
668
+ * @returns formatted html code
669
+ * @private withing the package because of HUGE size of prettier dependency
697
670
  */
698
- let DEFAULT_IS_VERBOSE = false;
671
+ function prettifyMarkdown(content) {
672
+ try {
673
+ return prettier.format(content, {
674
+ parser: 'markdown',
675
+ plugins: [parserHtml__default["default"]],
676
+ // TODO: DRY - make some import or auto-copy of .prettierrc
677
+ endOfLine: 'lf',
678
+ tabWidth: 4,
679
+ singleQuote: true,
680
+ trailingComma: 'all',
681
+ arrowParens: 'always',
682
+ printWidth: 120,
683
+ htmlWhitespaceSensitivity: 'ignore',
684
+ jsxBracketSameLine: false,
685
+ bracketSpacing: true,
686
+ });
687
+ }
688
+ catch (error) {
689
+ // TODO: [🟥] Detect browser / node and make it colorfull
690
+ console.error('There was an error with prettifying the markdown, using the original as the fallback', {
691
+ error,
692
+ html: content,
693
+ });
694
+ return content;
695
+ }
696
+ }
697
+
699
698
  /**
700
- * @@@
699
+ * Converts promptbook in JSON format to string format
701
700
  *
701
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
702
+ * @param pipelineJson Promptbook in JSON format (.bookc)
703
+ * @returns Promptbook in string format (.book.md)
702
704
  * @public exported from `@promptbook/core`
703
705
  */
704
- const DEFAULT_IS_AUTO_INSTALLED = false;
706
+ function pipelineJsonToString(pipelineJson) {
707
+ const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
708
+ let pipelineString = `# ${title}`;
709
+ if (description) {
710
+ pipelineString += '\n\n';
711
+ pipelineString += description;
712
+ }
713
+ const commands = [];
714
+ if (pipelineUrl) {
715
+ commands.push(`PIPELINE URL ${pipelineUrl}`);
716
+ }
717
+ if (bookVersion !== `undefined`) {
718
+ commands.push(`BOOK VERSION ${bookVersion}`);
719
+ }
720
+ // TODO: [main] !!5 This increases size of the bundle and is probbably not necessary
721
+ pipelineString = prettifyMarkdown(pipelineString);
722
+ for (const parameter of parameters.filter(({ isInput }) => isInput)) {
723
+ commands.push(`INPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
724
+ }
725
+ for (const parameter of parameters.filter(({ isOutput }) => isOutput)) {
726
+ commands.push(`OUTPUT PARAMETER ${taskParameterJsonToString(parameter)}`);
727
+ }
728
+ pipelineString += '\n\n';
729
+ pipelineString += commands.map((command) => `- ${command}`).join('\n');
730
+ for (const task of tasks) {
731
+ const {
732
+ /* Note: Not using:> name, */
733
+ title, description,
734
+ /* Note: dependentParameterNames, */
735
+ jokerParameterNames: jokers, taskType, content, postprocessingFunctionNames: postprocessing, expectations, format, resultingParameterName, } = task;
736
+ pipelineString += '\n\n';
737
+ pipelineString += `## ${title}`;
738
+ if (description) {
739
+ pipelineString += '\n\n';
740
+ pipelineString += description;
741
+ }
742
+ const commands = [];
743
+ let contentLanguage = 'text';
744
+ if (taskType === 'PROMPT_TASK') {
745
+ const { modelRequirements } = task;
746
+ const { modelName, modelVariant } = modelRequirements || {};
747
+ // Note: Do nothing, it is default
748
+ // commands.push(`PROMPT`);
749
+ if (modelVariant) {
750
+ commands.push(`MODEL VARIANT ${capitalize(modelVariant)}`);
751
+ }
752
+ if (modelName) {
753
+ commands.push(`MODEL NAME \`${modelName}\``);
754
+ }
755
+ }
756
+ else if (taskType === 'SIMPLE_TASK') {
757
+ commands.push(`SIMPLE TEMPLATE`);
758
+ // Note: Nothing special here
759
+ }
760
+ else if (taskType === 'SCRIPT_TASK') {
761
+ commands.push(`SCRIPT`);
762
+ if (task.contentLanguage) {
763
+ contentLanguage = task.contentLanguage;
764
+ }
765
+ else {
766
+ contentLanguage = '';
767
+ }
768
+ }
769
+ else if (taskType === 'DIALOG_TASK') {
770
+ commands.push(`DIALOG`);
771
+ // Note: Nothing special here
772
+ } // <- }else if([🅱]
773
+ if (jokers) {
774
+ for (const joker of jokers) {
775
+ commands.push(`JOKER {${joker}}`);
776
+ }
777
+ } /* not else */
778
+ if (postprocessing) {
779
+ for (const postprocessingFunctionName of postprocessing) {
780
+ commands.push(`POSTPROCESSING \`${postprocessingFunctionName}\``);
781
+ }
782
+ } /* not else */
783
+ if (expectations) {
784
+ for (const [unit, { min, max }] of Object.entries(expectations)) {
785
+ if (min === max) {
786
+ commands.push(`EXPECT EXACTLY ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
787
+ }
788
+ else {
789
+ if (min !== undefined) {
790
+ commands.push(`EXPECT MIN ${min} ${capitalize(unit + (min > 1 ? 's' : ''))}`);
791
+ } /* not else */
792
+ if (max !== undefined) {
793
+ commands.push(`EXPECT MAX ${max} ${capitalize(unit + (max > 1 ? 's' : ''))}`);
794
+ }
795
+ }
796
+ }
797
+ } /* not else */
798
+ if (format) {
799
+ if (format === 'JSON') {
800
+ // TODO: @deprecated remove
801
+ commands.push(`FORMAT JSON`);
802
+ }
803
+ } /* not else */
804
+ pipelineString += '\n\n';
805
+ pipelineString += commands.map((command) => `- ${command}`).join('\n');
806
+ pipelineString += '\n\n';
807
+ pipelineString += '```' + contentLanguage;
808
+ pipelineString += '\n';
809
+ pipelineString += spaceTrim__default["default"](content);
810
+ // <- TODO: [main] !!3 Escape
811
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
812
+ pipelineString += '\n';
813
+ pipelineString += '```';
814
+ pipelineString += '\n\n';
815
+ pipelineString += `\`-> {${resultingParameterName}}\``; // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
816
+ }
817
+ return validatePipelineString(pipelineString);
818
+ }
705
819
  /**
706
- * @@@
707
- *
708
- * @private within the repository
820
+ * @private internal utility of `pipelineJsonToString`
709
821
  */
710
- const IS_PIPELINE_LOGIC_VALIDATED = just(
711
- /**/
712
- // Note: In normal situations, we check the pipeline logic:
713
- true);
822
+ function taskParameterJsonToString(taskParameterJson) {
823
+ const { name, description } = taskParameterJson;
824
+ let parameterString = `{${name}}`;
825
+ if (description) {
826
+ parameterString = `${parameterString} ${description}`;
827
+ }
828
+ return parameterString;
829
+ }
714
830
  /**
715
- * Note: [💞] Ignore a discrepancy between file name and entity name
716
- * TODO: [🧠][🧜‍♂️] Maybe join remoteServerUrl and path into single value
831
+ * TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
832
+ * TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
833
+ * TODO: [🏛] Maybe make some markdown builder
834
+ * TODO: [🏛] Escape all
835
+ * TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
717
836
  */
718
837
 
719
838
  /**
@@ -758,74 +877,6 @@
758
877
  * TODO: [🧠] Is there a way how to meaningfully test this utility
759
878
  */
760
879
 
761
- /**
762
- * Make error report URL for the given error
763
- *
764
- * @private private within the repository
765
- */
766
- function getErrorReportUrl(error) {
767
- const report = {
768
- title: `🐜 Error report from ${NAME}`,
769
- body: spaceTrim__default["default"]((block) => `
770
-
771
-
772
- \`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
773
-
774
- \`\`\`
775
- ${block(error.message || '(no error message)')}
776
- \`\`\`
777
-
778
-
779
- ## More info:
780
-
781
- - **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
782
- - **Book language version:** ${BOOK_LANGUAGE_VERSION}
783
- - **Time:** ${new Date().toISOString()}
784
-
785
- <details>
786
- <summary>Stack trace:</summary>
787
-
788
- ## Stack trace:
789
-
790
- \`\`\`stacktrace
791
- ${block(error.stack || '(empty)')}
792
- \`\`\`
793
- </details>
794
-
795
- `),
796
- };
797
- const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
798
- reportUrl.searchParams.set('labels', 'bug');
799
- reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
800
- reportUrl.searchParams.set('title', report.title);
801
- reportUrl.searchParams.set('body', report.body);
802
- return reportUrl;
803
- }
804
-
805
- /**
806
- * This error type indicates that the error should not happen and its last check before crashing with some other error
807
- *
808
- * @public exported from `@promptbook/core`
809
- */
810
- class UnexpectedError extends Error {
811
- constructor(message) {
812
- super(spaceTrim.spaceTrim((block) => `
813
- ${block(message)}
814
-
815
- Note: This error should not happen.
816
- It's probbably a bug in the pipeline collection
817
-
818
- Please report issue:
819
- ${block(getErrorReportUrl(new Error(message)).href)}
820
-
821
- Or contact us on ${ADMIN_EMAIL}
822
-
823
- `));
824
- this.name = 'UnexpectedError';
825
- Object.setPrototypeOf(this, UnexpectedError.prototype);
826
- }
827
- }
828
-
829
880
  /**
830
881
  * Checks if the value is [🚉] serializable as JSON
831
882
  * If not, throws an UnexpectedError with a rich error message and tracking
@@ -917,9 +968,7 @@
917
968
  JSON.stringify(value); // <- TODO: [0]
918
969
  }
919
970
  catch (error) {
920
- if (!(error instanceof Error)) {
921
- throw error;
922
- }
971
+ assertsError(error);
923
972
  throw new UnexpectedError(spaceTrim__default["default"]((block) => `
924
973
  \`${name}\` is not serializable
925
974
 
@@ -1708,7 +1757,7 @@
1708
1757
  }
1709
1758
  }
1710
1759
  /**
1711
- * TODO: !!!!!! Add id to all errors
1760
+ * TODO: [🧠][🌂] Add id to all errors
1712
1761
  */
1713
1762
 
1714
1763
  /**
@@ -1912,6 +1961,19 @@
1912
1961
  }
1913
1962
  }
1914
1963
 
1964
+ /**
1965
+ * Error thrown when a fetch request fails
1966
+ *
1967
+ * @public exported from `@promptbook/core`
1968
+ */
1969
+ class PromptbookFetchError extends Error {
1970
+ constructor(message) {
1971
+ super(message);
1972
+ this.name = 'PromptbookFetchError';
1973
+ Object.setPrototypeOf(this, PromptbookFetchError.prototype);
1974
+ }
1975
+ }
1976
+
1915
1977
  /**
1916
1978
  * Index of all custom errors
1917
1979
  *
@@ -1932,7 +1994,10 @@
1932
1994
  PipelineExecutionError,
1933
1995
  PipelineLogicError,
1934
1996
  PipelineUrlError,
1997
+ AuthenticationError,
1998
+ PromptbookFetchError,
1935
1999
  UnexpectedError,
2000
+ WrappedError,
1936
2001
  // TODO: [🪑]> VersionMismatchError,
1937
2002
  };
1938
2003
  /**
@@ -1949,7 +2014,6 @@
1949
2014
  TypeError,
1950
2015
  URIError,
1951
2016
  AggregateError,
1952
- AuthenticationError,
1953
2017
  /*
1954
2018
  Note: Not widely supported
1955
2019
  > InternalError,
@@ -2072,8 +2136,8 @@
2072
2136
  updatedAt = new Date();
2073
2137
  errors.push(...executionResult.errors);
2074
2138
  warnings.push(...executionResult.warnings);
2075
- // <- TODO: !!! Only unique errors and warnings should be added (or filtered)
2076
- // TODO: [🧠] !!! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
2139
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
2140
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
2077
2141
  // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
2078
2142
  // And delete `ExecutionTask.currentValue.preparedPipeline`
2079
2143
  assertsTaskSuccessful(executionResult);
@@ -2083,6 +2147,7 @@
2083
2147
  partialResultSubject.next(executionResult);
2084
2148
  }
2085
2149
  catch (error) {
2150
+ assertsError(error);
2086
2151
  status = 'ERROR';
2087
2152
  errors.push(error);
2088
2153
  partialResultSubject.error(error);
@@ -2474,14 +2539,15 @@
2474
2539
  }
2475
2540
  }
2476
2541
  catch (error) {
2477
- if (!(error instanceof Error) || error instanceof UnexpectedError) {
2542
+ assertsError(error);
2543
+ if (error instanceof UnexpectedError) {
2478
2544
  throw error;
2479
2545
  }
2480
2546
  errors.push({ llmExecutionTools, error });
2481
2547
  }
2482
2548
  }
2483
2549
  if (errors.length === 1) {
2484
- throw errors[0];
2550
+ throw errors[0].error;
2485
2551
  }
2486
2552
  else if (errors.length > 1) {
2487
2553
  throw new PipelineExecutionError(
@@ -3315,17 +3381,22 @@
3315
3381
  /**
3316
3382
  * The built-in `fetch' function with a lightweight error handling wrapper as default fetch function used in Promptbook scrapers
3317
3383
  *
3318
- * @private as default `fetch` function used in Promptbook scrapers
3384
+ * @public exported from `@promptbook/core`
3319
3385
  */
3320
- const scraperFetch = async (url, init) => {
3386
+ const promptbookFetch = async (urlOrRequest, init) => {
3321
3387
  try {
3322
- return await fetch(url, init);
3388
+ return await fetch(urlOrRequest, init);
3323
3389
  }
3324
3390
  catch (error) {
3325
- if (!(error instanceof Error)) {
3326
- throw error;
3391
+ assertsError(error);
3392
+ let url;
3393
+ if (typeof urlOrRequest === 'string') {
3394
+ url = urlOrRequest;
3395
+ }
3396
+ else if (urlOrRequest instanceof Request) {
3397
+ url = urlOrRequest.url;
3327
3398
  }
3328
- throw new KnowledgeScrapeError(spaceTrim__default["default"]((block) => `
3399
+ throw new PromptbookFetchError(spaceTrim__default["default"]((block) => `
3329
3400
  Can not fetch "${url}"
3330
3401
 
3331
3402
  Fetch error:
@@ -3346,7 +3417,7 @@
3346
3417
  async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
3347
3418
  // console.log('!! makeKnowledgeSourceHandler', knowledgeSource);
3348
3419
  var _a;
3349
- const { fetch = scraperFetch } = tools;
3420
+ const { fetch = promptbookFetch } = tools;
3350
3421
  const { knowledgeSourceContent } = knowledgeSource;
3351
3422
  let { name } = knowledgeSource;
3352
3423
  const { rootDirname = null,
@@ -3548,9 +3619,7 @@
3548
3619
  knowledgePreparedUnflatten[index] = pieces;
3549
3620
  }
3550
3621
  catch (error) {
3551
- if (!(error instanceof Error)) {
3552
- throw error;
3553
- }
3622
+ assertsError(error);
3554
3623
  console.warn(error);
3555
3624
  // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
3556
3625
  }
@@ -3842,13 +3911,19 @@
3842
3911
  return value.toISOString();
3843
3912
  }
3844
3913
  else {
3845
- return JSON.stringify(value);
3914
+ try {
3915
+ return JSON.stringify(value);
3916
+ }
3917
+ catch (error) {
3918
+ if (error instanceof TypeError && error.message.includes('circular structure')) {
3919
+ return VALUE_STRINGS.circular;
3920
+ }
3921
+ throw error;
3922
+ }
3846
3923
  }
3847
3924
  }
3848
3925
  catch (error) {
3849
- if (!(error instanceof Error)) {
3850
- throw error;
3851
- }
3926
+ assertsError(error);
3852
3927
  console.error(error);
3853
3928
  return VALUE_STRINGS.unserializable;
3854
3929
  }
@@ -3905,9 +3980,7 @@
3905
3980
  }
3906
3981
  }
3907
3982
  catch (error) {
3908
- if (!(error instanceof Error)) {
3909
- throw error;
3910
- }
3983
+ assertsError(error);
3911
3984
  throw new ParseError(spaceTrim.spaceTrim((block) => `
3912
3985
  Can not extract variables from the script
3913
3986
  ${block(error.stack || error.message)}
@@ -4026,6 +4099,28 @@
4026
4099
  // encoding: 'utf-8',
4027
4100
  });
4028
4101
 
4102
+ /**
4103
+ * Function to check if a string is valid CSV
4104
+ *
4105
+ * @param value The string to check
4106
+ * @returns True if the string is a valid CSV string, false otherwise
4107
+ *
4108
+ * @public exported from `@promptbook/utils`
4109
+ */
4110
+ function isValidCsvString(value) {
4111
+ try {
4112
+ // A simple check for CSV format: at least one comma and no invalid characters
4113
+ if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
4114
+ return true;
4115
+ }
4116
+ return false;
4117
+ }
4118
+ catch (error) {
4119
+ assertsError(error);
4120
+ return false;
4121
+ }
4122
+ }
4123
+
4029
4124
  /**
4030
4125
  * Definition for CSV spreadsheet
4031
4126
  *
@@ -4036,7 +4131,7 @@
4036
4131
  formatName: 'CSV',
4037
4132
  aliases: ['SPREADSHEET', 'TABLE'],
4038
4133
  isValid(value, settings, schema) {
4039
- return true;
4134
+ return isValidCsvString(value);
4040
4135
  },
4041
4136
  canBeValid(partialValue, settings, schema) {
4042
4137
  return true;
@@ -4190,6 +4285,30 @@
4190
4285
  * TODO: [🏢] Allow to expect something inside each item of list and other formats
4191
4286
  */
4192
4287
 
4288
+ /**
4289
+ * Function to check if a string is valid XML
4290
+ *
4291
+ * @param value
4292
+ * @returns True if the string is a valid XML string, false otherwise
4293
+ *
4294
+ * @public exported from `@promptbook/utils`
4295
+ */
4296
+ function isValidXmlString(value) {
4297
+ try {
4298
+ const parser = new DOMParser();
4299
+ const parsedDocument = parser.parseFromString(value, 'application/xml');
4300
+ const parserError = parsedDocument.getElementsByTagName('parsererror');
4301
+ if (parserError.length > 0) {
4302
+ return false;
4303
+ }
4304
+ return true;
4305
+ }
4306
+ catch (error) {
4307
+ assertsError(error);
4308
+ return false;
4309
+ }
4310
+ }
4311
+
4193
4312
  /**
4194
4313
  * Definition for XML format
4195
4314
  *
@@ -4199,7 +4318,7 @@
4199
4318
  formatName: 'XML',
4200
4319
  mimeType: 'application/xml',
4201
4320
  isValid(value, settings, schema) {
4202
- return true;
4321
+ return isValidXmlString(value);
4203
4322
  },
4204
4323
  canBeValid(partialValue, settings, schema) {
4205
4324
  return true;
@@ -4670,9 +4789,7 @@
4670
4789
  break scripts;
4671
4790
  }
4672
4791
  catch (error) {
4673
- if (!(error instanceof Error)) {
4674
- throw error;
4675
- }
4792
+ assertsError(error);
4676
4793
  if (error instanceof UnexpectedError) {
4677
4794
  throw error;
4678
4795
  }
@@ -4742,9 +4859,7 @@
4742
4859
  break scripts;
4743
4860
  }
4744
4861
  catch (error) {
4745
- if (!(error instanceof Error)) {
4746
- throw error;
4747
- }
4862
+ assertsError(error);
4748
4863
  if (error instanceof UnexpectedError) {
4749
4864
  throw error;
4750
4865
  }
@@ -5365,9 +5480,7 @@
5365
5480
  await Promise.all(resolving);
5366
5481
  }
5367
5482
  catch (error /* <- Note: [3] */) {
5368
- if (!(error instanceof Error)) {
5369
- throw error;
5370
- }
5483
+ assertsError(error);
5371
5484
  // Note: No need to rethrow UnexpectedError
5372
5485
  // if (error instanceof UnexpectedError) {
5373
5486
  // Note: Count usage, [🧠] Maybe put to separate function executionReportJsonToUsage + DRY [🤹‍♂️]