@promptbook/node 0.86.6 → 0.86.10

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.
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('fs/promises'), require('path'), require('spacetrim'), require('prettier'), require('prettier/parser-html'), require('rxjs'), require('crypto'), require('waitasecond'), require('papaparse'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto-js'), require('mime-types'), require('child_process'), require('dotenv')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'colors', 'fs/promises', 'path', 'spacetrim', 'prettier', 'prettier/parser-html', 'rxjs', 'crypto', 'waitasecond', 'papaparse', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto-js', 'mime-types', 'child_process', 'dotenv'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-node"] = {}, global.colors, global.promises, global.path, global.spaceTrim, global.prettier, global.parserHtml, global.rxjs, global.crypto, global.waitasecond, global.papaparse, global.hexEncoder, global.sha256, global.cryptoJs, global.mimeTypes, global.child_process, global.dotenv));
5
- })(this, (function (exports, colors, promises, path, spaceTrim, prettier, parserHtml, rxjs, crypto, waitasecond, papaparse, hexEncoder, sha256, cryptoJs, mimeTypes, child_process, dotenv) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('fs/promises'), require('path'), require('spacetrim'), require('jszip'), require('prettier'), require('prettier/parser-html'), require('rxjs'), require('crypto'), require('waitasecond'), require('papaparse'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto-js'), require('mime-types'), require('child_process'), require('dotenv')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'colors', 'fs/promises', 'path', 'spacetrim', 'jszip', 'prettier', 'prettier/parser-html', 'rxjs', 'crypto', 'waitasecond', 'papaparse', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto-js', 'mime-types', 'child_process', 'dotenv'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-node"] = {}, global.colors, global.promises, global.path, global.spaceTrim, global.JSZip, global.prettier, global.parserHtml, global.rxjs, global.crypto, global.waitasecond, global.papaparse, global.hexEncoder, global.sha256, global.cryptoJs, global.mimeTypes, global.child_process, global.dotenv));
5
+ })(this, (function (exports, colors, promises, path, spaceTrim, JSZip, prettier, parserHtml, rxjs, crypto, waitasecond, papaparse, hexEncoder, sha256, cryptoJs, mimeTypes, child_process, dotenv) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -26,6 +26,7 @@
26
26
 
27
27
  var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
28
28
  var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim);
29
+ var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
29
30
  var parserHtml__default = /*#__PURE__*/_interopDefaultLegacy(parserHtml);
30
31
  var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
31
32
  var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
@@ -45,7 +46,7 @@
45
46
  * @generated
46
47
  * @see https://github.com/webgptorg/promptbook
47
48
  */
48
- var PROMPTBOOK_ENGINE_VERSION = '0.86.6';
49
+ var PROMPTBOOK_ENGINE_VERSION = '0.86.10';
49
50
  /**
50
51
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
51
52
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -353,1119 +354,1174 @@
353
354
  * TODO: [🧠][🧜‍♂️] Maybe join remoteUrl and path into single value
354
355
  */
355
356
 
356
- 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"}];
357
-
358
357
  /**
359
- * Checks if value is valid email
358
+ * Make error report URL for the given error
360
359
  *
361
- * @public exported from `@promptbook/utils`
360
+ * @private private within the repository
362
361
  */
363
- function isValidEmail(email) {
364
- if (typeof email !== 'string') {
365
- return false;
366
- }
367
- if (email.split('\n').length > 1) {
368
- return false;
369
- }
370
- return /^.+@.+\..+$/.test(email);
362
+ function getErrorReportUrl(error) {
363
+ var report = {
364
+ title: "\uD83D\uDC1C Error report from ".concat(NAME),
365
+ body: spaceTrim__default["default"](function (block) { return "\n\n\n `".concat(error.name || 'Error', "` has occurred in the [").concat(NAME, "], please look into it @").concat(ADMIN_GITHUB_NAME, ".\n\n ```\n ").concat(block(error.message || '(no error message)'), "\n ```\n\n\n ## More info:\n\n - **Promptbook engine version:** ").concat(PROMPTBOOK_ENGINE_VERSION, "\n - **Book language version:** ").concat(BOOK_LANGUAGE_VERSION, "\n - **Time:** ").concat(new Date().toISOString(), "\n\n <details>\n <summary>Stack trace:</summary>\n\n ## Stack trace:\n\n ```stacktrace\n ").concat(block(error.stack || '(empty)'), "\n ```\n </details>\n\n "); }),
366
+ };
367
+ var reportUrl = new URL("https://github.com/webgptorg/promptbook/issues/new");
368
+ reportUrl.searchParams.set('labels', 'bug');
369
+ reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
370
+ reportUrl.searchParams.set('title', report.title);
371
+ reportUrl.searchParams.set('body', report.body);
372
+ return reportUrl;
371
373
  }
372
374
 
373
375
  /**
374
- * Tests if given string is valid URL.
376
+ * This error type indicates that the error should not happen and its last check before crashing with some other error
375
377
  *
376
- * Note: This does not check if the file exists only if the path is valid
377
- * @public exported from `@promptbook/utils`
378
+ * @public exported from `@promptbook/core`
378
379
  */
379
- function isValidFilePath(filename) {
380
- if (typeof filename !== 'string') {
381
- return false;
382
- }
383
- if (filename.split('\n').length > 1) {
384
- return false;
385
- }
386
- if (filename.split(' ').length >
387
- 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
388
- return false;
389
- }
390
- var filenameSlashes = filename.split('\\').join('/');
391
- // Absolute Unix path: /hello.txt
392
- if (/^(\/)/i.test(filenameSlashes)) {
393
- // console.log(filename, 'Absolute Unix path: /hello.txt');
394
- return true;
395
- }
396
- // Absolute Windows path: /hello.txt
397
- if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
398
- // console.log(filename, 'Absolute Windows path: /hello.txt');
399
- return true;
400
- }
401
- // Relative path: ./hello.txt
402
- if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
403
- // console.log(filename, 'Relative path: ./hello.txt');
404
- return true;
405
- }
406
- // Allow paths like foo/hello
407
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
408
- // console.log(filename, 'Allow paths like foo/hello');
409
- return true;
410
- }
411
- // Allow paths like hello.book
412
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
413
- // console.log(filename, 'Allow paths like hello.book');
414
- return true;
380
+ var UnexpectedError = /** @class */ (function (_super) {
381
+ __extends(UnexpectedError, _super);
382
+ function UnexpectedError(message) {
383
+ var _this = _super.call(this, spaceTrim.spaceTrim(function (block) { return "\n ".concat(block(message), "\n\n Note: This error should not happen.\n It's probbably a bug in the pipeline collection\n\n Please report issue:\n ").concat(block(getErrorReportUrl(new Error(message)).href), "\n\n Or contact us on ").concat(ADMIN_EMAIL, "\n\n "); })) || this;
384
+ _this.name = 'UnexpectedError';
385
+ Object.setPrototypeOf(_this, UnexpectedError.prototype);
386
+ return _this;
415
387
  }
416
- return false;
417
- }
388
+ return UnexpectedError;
389
+ }(Error));
390
+
418
391
  /**
419
- * TODO: [🍏] Implement for MacOs
392
+ * Orders JSON object by keys
393
+ *
394
+ * @returns The same type of object as the input re-ordered
395
+ * @public exported from `@promptbook/utils`
420
396
  */
397
+ function orderJson(options) {
398
+ var value = options.value, order = options.order;
399
+ var orderedValue = __assign(__assign({}, (order === undefined ? {} : Object.fromEntries(order.map(function (key) { return [key, undefined]; })))), value);
400
+ return orderedValue;
401
+ }
421
402
 
422
403
  /**
423
- * Tests if given string is valid URL.
404
+ * Freezes the given object and all its nested objects recursively
424
405
  *
425
- * Note: Dataurl are considered perfectly valid.
426
- * Note: There are two simmilar functions:
427
- * - `isValidUrl` which tests any URL
428
- * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
406
+ * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
407
+ * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
429
408
  *
409
+ * @returns The same object as the input, but deeply frozen
430
410
  * @public exported from `@promptbook/utils`
431
411
  */
432
- function isValidUrl(url) {
433
- if (typeof url !== 'string') {
434
- return false;
412
+ function $deepFreeze(objectValue) {
413
+ var e_1, _a;
414
+ if (Array.isArray(objectValue)) {
415
+ return Object.freeze(objectValue.map(function (item) { return $deepFreeze(item); }));
435
416
  }
417
+ var propertyNames = Object.getOwnPropertyNames(objectValue);
436
418
  try {
437
- if (url.startsWith('blob:')) {
438
- url = url.replace(/^blob:/, '');
439
- }
440
- var urlObject = new URL(url /* because fail is handled */);
441
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
442
- return false;
419
+ for (var propertyNames_1 = __values(propertyNames), propertyNames_1_1 = propertyNames_1.next(); !propertyNames_1_1.done; propertyNames_1_1 = propertyNames_1.next()) {
420
+ var propertyName = propertyNames_1_1.value;
421
+ var value = objectValue[propertyName];
422
+ if (value && typeof value === 'object') {
423
+ $deepFreeze(value);
424
+ }
443
425
  }
444
- return true;
445
426
  }
446
- catch (error) {
447
- return false;
427
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
428
+ finally {
429
+ try {
430
+ if (propertyNames_1_1 && !propertyNames_1_1.done && (_a = propertyNames_1.return)) _a.call(propertyNames_1);
431
+ }
432
+ finally { if (e_1) throw e_1.error; }
448
433
  }
434
+ Object.freeze(objectValue);
435
+ return objectValue;
449
436
  }
450
-
451
- /**
452
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
453
- *
454
- * @public exported from `@promptbook/core`
455
- */
456
- var ParseError = /** @class */ (function (_super) {
457
- __extends(ParseError, _super);
458
- function ParseError(message) {
459
- var _this = _super.call(this, message) || this;
460
- _this.name = 'ParseError';
461
- Object.setPrototypeOf(_this, ParseError.prototype);
462
- return _this;
463
- }
464
- return ParseError;
465
- }(Error));
466
437
  /**
467
- * TODO: Maybe split `ParseError` and `ApplyError`
438
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
468
439
  */
469
440
 
470
441
  /**
471
- * Function isValidJsonString will tell you if the string is valid JSON or not
442
+ * Checks if the value is [🚉] serializable as JSON
443
+ * If not, throws an UnexpectedError with a rich error message and tracking
444
+ *
445
+ * - Almost all primitives are serializable BUT:
446
+ * - `undefined` is not serializable
447
+ * - `NaN` is not serializable
448
+ * - Objects and arrays are serializable if all their properties are serializable
449
+ * - Functions are not serializable
450
+ * - Circular references are not serializable
451
+ * - `Date` objects are not serializable
452
+ * - `Map` and `Set` objects are not serializable
453
+ * - `RegExp` objects are not serializable
454
+ * - `Error` objects are not serializable
455
+ * - `Symbol` objects are not serializable
456
+ * - And much more...
472
457
  *
458
+ * @throws UnexpectedError if the value is not serializable as JSON
473
459
  * @public exported from `@promptbook/utils`
474
460
  */
475
- function isValidJsonString(value /* <- [👨‍⚖️] */) {
476
- try {
477
- JSON.parse(value);
478
- return true;
461
+ function checkSerializableAsJson(options) {
462
+ var e_1, _a;
463
+ var value = options.value, name = options.name, message = options.message;
464
+ if (value === undefined) {
465
+ throw new UnexpectedError("".concat(name, " is undefined"));
479
466
  }
480
- catch (error) {
481
- if (!(error instanceof Error)) {
482
- throw error;
483
- }
484
- if (error.message.includes('Unexpected token')) {
485
- return false;
486
- }
487
- return false;
467
+ else if (value === null) {
468
+ return;
488
469
  }
489
- }
490
-
491
- /**
492
- * Function `validatePipelineString` will validate the if the string is a valid pipeline string
493
- * 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.
494
- *
495
- * @param {string} pipelineString the candidate for a pipeline string
496
- * @returns {PipelineString} the same string as input, but validated as valid
497
- * @throws {ParseError} if the string is not a valid pipeline string
498
- * @public exported from `@promptbook/core`
499
- */
500
- function validatePipelineString(pipelineString) {
501
- if (isValidJsonString(pipelineString)) {
502
- throw new ParseError('Expected a book, but got a JSON string');
470
+ else if (typeof value === 'boolean') {
471
+ return;
503
472
  }
504
- else if (isValidUrl(pipelineString)) {
505
- throw new ParseError("Expected a book, but got just the URL \"".concat(pipelineString, "\""));
473
+ else if (typeof value === 'number' && !isNaN(value)) {
474
+ return;
506
475
  }
507
- else if (isValidFilePath(pipelineString)) {
508
- throw new ParseError("Expected a book, but got just the file path \"".concat(pipelineString, "\""));
476
+ else if (typeof value === 'string') {
477
+ return;
509
478
  }
510
- else if (isValidEmail(pipelineString)) {
511
- throw new ParseError("Expected a book, but got just the email \"".concat(pipelineString, "\""));
479
+ else if (typeof value === 'symbol') {
480
+ throw new UnexpectedError("".concat(name, " is symbol"));
512
481
  }
513
- // <- TODO: Implement the validation + add tests when the pipeline logic considered as invalid
514
- return pipelineString;
515
- }
516
- /**
517
- * TODO: [🧠][🈴] Where is the best location for this file
518
- */
519
-
520
- /**
521
- * Prettify the html code
522
- *
523
- * @param content raw html code
524
- * @returns formatted html code
525
- * @private withing the package because of HUGE size of prettier dependency
526
- */
527
- function prettifyMarkdown(content) {
528
- try {
529
- return prettier.format(content, {
530
- parser: 'markdown',
531
- plugins: [parserHtml__default["default"]],
532
- // TODO: DRY - make some import or auto-copy of .prettierrc
533
- endOfLine: 'lf',
534
- tabWidth: 4,
535
- singleQuote: true,
536
- trailingComma: 'all',
537
- arrowParens: 'always',
538
- printWidth: 120,
539
- htmlWhitespaceSensitivity: 'ignore',
540
- jsxBracketSameLine: false,
541
- bracketSpacing: true,
542
- });
482
+ else if (typeof value === 'function') {
483
+ throw new UnexpectedError("".concat(name, " is function"));
543
484
  }
544
- catch (error) {
545
- // TODO: [🟥] Detect browser / node and make it colorfull
546
- console.error('There was an error with prettifying the markdown, using the original as the fallback', {
547
- error: error,
548
- html: content,
549
- });
550
- return content;
485
+ else if (typeof value === 'object' && Array.isArray(value)) {
486
+ for (var i = 0; i < value.length; i++) {
487
+ checkSerializableAsJson({ name: "".concat(name, "[").concat(i, "]"), value: value[i], message: message });
488
+ }
551
489
  }
552
- }
553
-
554
- /**
555
- * Makes first letter of a string uppercase
556
- *
557
- * @public exported from `@promptbook/utils`
558
- */
559
- function capitalize(word) {
560
- return word.substring(0, 1).toUpperCase() + word.substring(1);
561
- }
562
-
563
- /**
564
- * Converts promptbook in JSON format to string format
565
- *
566
- * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
567
- * @param pipelineJson Promptbook in JSON format (.bookc)
568
- * @returns Promptbook in string format (.book.md)
569
- * @public exported from `@promptbook/core`
570
- */
571
- function pipelineJsonToString(pipelineJson) {
572
- var e_1, _a, e_2, _b, e_3, _c, e_4, _d, e_5, _e, e_6, _f;
573
- var title = pipelineJson.title, pipelineUrl = pipelineJson.pipelineUrl, bookVersion = pipelineJson.bookVersion, description = pipelineJson.description, parameters = pipelineJson.parameters, tasks = pipelineJson.tasks;
574
- var pipelineString = "# ".concat(title);
575
- if (description) {
576
- pipelineString += '\n\n';
577
- pipelineString += description;
578
- }
579
- var commands = [];
580
- if (pipelineUrl) {
581
- commands.push("PIPELINE URL ".concat(pipelineUrl));
582
- }
583
- if (bookVersion !== "undefined") {
584
- commands.push("BOOK VERSION ".concat(bookVersion));
585
- }
586
- // TODO: [main] !!5 This increases size of the bundle and is probbably not necessary
587
- pipelineString = prettifyMarkdown(pipelineString);
588
- try {
589
- for (var _g = __values(parameters.filter(function (_a) {
590
- var isInput = _a.isInput;
591
- return isInput;
592
- })), _h = _g.next(); !_h.done; _h = _g.next()) {
593
- var parameter = _h.value;
594
- commands.push("INPUT PARAMETER ".concat(taskParameterJsonToString(parameter)));
490
+ else if (typeof value === 'object') {
491
+ if (value instanceof Date) {
492
+ throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is Date\n\n Use `string_date_iso8601` instead\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n "); }));
595
493
  }
596
- }
597
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
598
- finally {
599
- try {
600
- if (_h && !_h.done && (_a = _g.return)) _a.call(_g);
494
+ else if (value instanceof Map) {
495
+ throw new UnexpectedError("".concat(name, " is Map"));
601
496
  }
602
- finally { if (e_1) throw e_1.error; }
603
- }
604
- try {
605
- for (var _j = __values(parameters.filter(function (_a) {
606
- var isOutput = _a.isOutput;
607
- return isOutput;
608
- })), _k = _j.next(); !_k.done; _k = _j.next()) {
609
- var parameter = _k.value;
610
- commands.push("OUTPUT PARAMETER ".concat(taskParameterJsonToString(parameter)));
497
+ else if (value instanceof Set) {
498
+ throw new UnexpectedError("".concat(name, " is Set"));
611
499
  }
612
- }
613
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
614
- finally {
615
- try {
616
- if (_k && !_k.done && (_b = _j.return)) _b.call(_j);
500
+ else if (value instanceof RegExp) {
501
+ throw new UnexpectedError("".concat(name, " is RegExp"));
617
502
  }
618
- finally { if (e_2) throw e_2.error; }
619
- }
620
- pipelineString += '\n\n';
621
- pipelineString += commands.map(function (command) { return "- ".concat(command); }).join('\n');
622
- try {
623
- for (var tasks_1 = __values(tasks), tasks_1_1 = tasks_1.next(); !tasks_1_1.done; tasks_1_1 = tasks_1.next()) {
624
- var task = tasks_1_1.value;
625
- var
626
- /* Note: Not using:> name, */
627
- title_1 = task.title, description_1 = task.description,
628
- /* Note: dependentParameterNames, */
629
- jokers = task.jokerParameterNames, taskType = task.taskType, content = task.content, postprocessing = task.postprocessingFunctionNames, expectations = task.expectations, format = task.format, resultingParameterName = task.resultingParameterName;
630
- pipelineString += '\n\n';
631
- pipelineString += "## ".concat(title_1);
632
- if (description_1) {
633
- pipelineString += '\n\n';
634
- pipelineString += description_1;
635
- }
636
- var commands_1 = [];
637
- var contentLanguage = 'text';
638
- if (taskType === 'PROMPT_TASK') {
639
- var modelRequirements = task.modelRequirements;
640
- var _l = modelRequirements || {}, modelName = _l.modelName, modelVariant = _l.modelVariant;
641
- // Note: Do nothing, it is default
642
- // commands.push(`PROMPT`);
643
- if (modelVariant) {
644
- commands_1.push("MODEL VARIANT ".concat(capitalize(modelVariant)));
503
+ else if (value instanceof Error) {
504
+ throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is unserialized Error\n\n Use function `serializeError`\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n\n "); }));
505
+ }
506
+ else {
507
+ try {
508
+ for (var _b = __values(Object.entries(value)), _c = _b.next(); !_c.done; _c = _b.next()) {
509
+ var _d = __read(_c.value, 2), subName = _d[0], subValue = _d[1];
510
+ if (subValue === undefined) {
511
+ // Note: undefined in object is serializable - it is just omited
512
+ continue;
513
+ }
514
+ checkSerializableAsJson({ name: "".concat(name, ".").concat(subName), value: subValue, message: message });
645
515
  }
646
- if (modelName) {
647
- commands_1.push("MODEL NAME `".concat(modelName, "`"));
516
+ }
517
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
518
+ finally {
519
+ try {
520
+ if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
648
521
  }
522
+ finally { if (e_1) throw e_1.error; }
649
523
  }
650
- else if (taskType === 'SIMPLE_TASK') {
651
- commands_1.push("SIMPLE TEMPLATE");
652
- // Note: Nothing special here
524
+ try {
525
+ JSON.stringify(value); // <- TODO: [0]
653
526
  }
654
- else if (taskType === 'SCRIPT_TASK') {
655
- commands_1.push("SCRIPT");
656
- if (task.contentLanguage) {
657
- contentLanguage = task.contentLanguage;
658
- }
659
- else {
660
- contentLanguage = '';
527
+ catch (error) {
528
+ if (!(error instanceof Error)) {
529
+ throw error;
661
530
  }
531
+ throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is not serializable\n\n ").concat(block(error.stack || error.message), "\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n "); }));
662
532
  }
663
- else if (taskType === 'DIALOG_TASK') {
664
- commands_1.push("DIALOG");
665
- // Note: Nothing special here
666
- } // <- }else if([🅱]
667
- if (jokers) {
668
- try {
669
- for (var jokers_1 = (e_4 = void 0, __values(jokers)), jokers_1_1 = jokers_1.next(); !jokers_1_1.done; jokers_1_1 = jokers_1.next()) {
670
- var joker = jokers_1_1.value;
671
- commands_1.push("JOKER {".concat(joker, "}"));
672
- }
673
- }
674
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
675
- finally {
676
- try {
677
- if (jokers_1_1 && !jokers_1_1.done && (_d = jokers_1.return)) _d.call(jokers_1);
678
- }
679
- finally { if (e_4) throw e_4.error; }
680
- }
681
- } /* not else */
682
- if (postprocessing) {
683
- try {
684
- for (var postprocessing_1 = (e_5 = void 0, __values(postprocessing)), postprocessing_1_1 = postprocessing_1.next(); !postprocessing_1_1.done; postprocessing_1_1 = postprocessing_1.next()) {
685
- var postprocessingFunctionName = postprocessing_1_1.value;
686
- commands_1.push("POSTPROCESSING `".concat(postprocessingFunctionName, "`"));
687
- }
688
- }
689
- catch (e_5_1) { e_5 = { error: e_5_1 }; }
690
- finally {
691
- try {
692
- if (postprocessing_1_1 && !postprocessing_1_1.done && (_e = postprocessing_1.return)) _e.call(postprocessing_1);
693
- }
694
- finally { if (e_5) throw e_5.error; }
695
- }
696
- } /* not else */
697
- if (expectations) {
698
- try {
699
- for (var _m = (e_6 = void 0, __values(Object.entries(expectations))), _o = _m.next(); !_o.done; _o = _m.next()) {
700
- var _p = __read(_o.value, 2), unit = _p[0], _q = _p[1], min = _q.min, max = _q.max;
701
- if (min === max) {
702
- commands_1.push("EXPECT EXACTLY ".concat(min, " ").concat(capitalize(unit + (min > 1 ? 's' : ''))));
703
- }
704
- else {
705
- if (min !== undefined) {
706
- commands_1.push("EXPECT MIN ".concat(min, " ").concat(capitalize(unit + (min > 1 ? 's' : ''))));
707
- } /* not else */
708
- if (max !== undefined) {
709
- commands_1.push("EXPECT MAX ".concat(max, " ").concat(capitalize(unit + (max > 1 ? 's' : ''))));
710
- }
711
- }
533
+ /*
534
+ TODO: [0] Is there some more elegant way to check circular references?
535
+ const seen = new Set();
536
+ const stack = [{ value }];
537
+ while (stack.length > 0) {
538
+ const { value } = stack.pop()!;
539
+ if (typeof value === 'object' && value !== null) {
540
+ if (seen.has(value)) {
541
+ throw new UnexpectedError(`${name} has circular reference`);
712
542
  }
713
- }
714
- catch (e_6_1) { e_6 = { error: e_6_1 }; }
715
- finally {
716
- try {
717
- if (_o && !_o.done && (_f = _m.return)) _f.call(_m);
543
+ seen.add(value);
544
+ if (Array.isArray(value)) {
545
+ stack.push(...value.map((value) => ({ value })));
546
+ } else {
547
+ stack.push(...Object.values(value).map((value) => ({ value })));
718
548
  }
719
- finally { if (e_6) throw e_6.error; }
720
549
  }
721
- } /* not else */
722
- if (format) {
723
- if (format === 'JSON') {
724
- // TODO: @deprecated remove
725
- commands_1.push("FORMAT JSON");
726
- }
727
- } /* not else */
728
- pipelineString += '\n\n';
729
- pipelineString += commands_1.map(function (command) { return "- ".concat(command); }).join('\n');
730
- pipelineString += '\n\n';
731
- pipelineString += '```' + contentLanguage;
732
- pipelineString += '\n';
733
- pipelineString += spaceTrim__default["default"](content);
734
- // <- TODO: [main] !!3 Escape
735
- // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
736
- pipelineString += '\n';
737
- pipelineString += '```';
738
- pipelineString += '\n\n';
739
- pipelineString += "`-> {".concat(resultingParameterName, "}`"); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
550
+ }
551
+ */
552
+ return;
740
553
  }
741
554
  }
742
- catch (e_3_1) { e_3 = { error: e_3_1 }; }
743
- finally {
744
- try {
745
- if (tasks_1_1 && !tasks_1_1.done && (_c = tasks_1.return)) _c.call(tasks_1);
746
- }
747
- finally { if (e_3) throw e_3.error; }
555
+ else {
556
+ throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is unknown type\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n "); }));
557
+ }
558
+ }
559
+ /**
560
+ * TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
561
+ * TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
562
+ * Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
563
+ */
564
+
565
+ /**
566
+ * @@@
567
+ *
568
+ * @public exported from `@promptbook/utils`
569
+ */
570
+ function deepClone(objectValue) {
571
+ return JSON.parse(JSON.stringify(objectValue));
572
+ /*
573
+ TODO: [🧠] Is there a better implementation?
574
+ > const propertyNames = Object.getOwnPropertyNames(objectValue);
575
+ > for (const propertyName of propertyNames) {
576
+ > const value = (objectValue as really_any)[propertyName];
577
+ > if (value && typeof value === 'object') {
578
+ > deepClone(value);
579
+ > }
580
+ > }
581
+ > return Object.assign({}, objectValue);
582
+ */
583
+ }
584
+ /**
585
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
586
+ */
587
+
588
+ /**
589
+ * Utility to export a JSON object from a function
590
+ *
591
+ * 1) Checks if the value is serializable as JSON
592
+ * 2) Makes a deep clone of the object
593
+ * 2) Orders the object properties
594
+ * 2) Deeply freezes the cloned object
595
+ *
596
+ * Note: This function does not mutates the given object
597
+ *
598
+ * @returns The same type of object as the input but read-only and re-ordered
599
+ * @public exported from `@promptbook/utils`
600
+ */
601
+ function exportJson(options) {
602
+ var name = options.name, value = options.value, order = options.order, message = options.message;
603
+ checkSerializableAsJson({ name: name, value: value, message: message });
604
+ var orderedValue =
605
+ // TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
606
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
607
+ // @ts-ignore
608
+ order === undefined
609
+ ? deepClone(value)
610
+ : orderJson({
611
+ value: value,
612
+ // <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
613
+ order: order,
614
+ });
615
+ $deepFreeze(orderedValue);
616
+ return orderedValue;
617
+ }
618
+ /**
619
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
620
+ */
621
+
622
+ /**
623
+ * Order of keys in the pipeline JSON
624
+ *
625
+ * @public exported from `@promptbook/core`
626
+ */
627
+ var ORDER_OF_PIPELINE_JSON = [
628
+ // Note: [🍙] In this order will be pipeline serialized
629
+ 'title',
630
+ 'pipelineUrl',
631
+ 'bookVersion',
632
+ 'description',
633
+ 'formfactorName',
634
+ 'parameters',
635
+ 'tasks',
636
+ 'personas',
637
+ 'preparations',
638
+ 'knowledgeSources',
639
+ 'knowledgePieces',
640
+ 'sources', // <- TODO: [🧠] Where should the `sources` be
641
+ ];
642
+ /**
643
+ * Nonce which is used for replacing things in strings
644
+ *
645
+ * @private within the repository
646
+ */
647
+ var REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
648
+ /**
649
+ * @@@
650
+ *
651
+ * @private within the repository
652
+ */
653
+ var RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
654
+ /**
655
+ * @@@
656
+ *
657
+ * @private within the repository
658
+ */
659
+ var RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
660
+ /**
661
+ * The names of the parameters that are reserved for special purposes
662
+ *
663
+ * @public exported from `@promptbook/core`
664
+ */
665
+ var RESERVED_PARAMETER_NAMES = exportJson({
666
+ name: 'RESERVED_PARAMETER_NAMES',
667
+ message: "The names of the parameters that are reserved for special purposes",
668
+ value: [
669
+ 'content',
670
+ 'context',
671
+ 'knowledge',
672
+ 'examples',
673
+ 'modelName',
674
+ 'currentDate',
675
+ // <- TODO: list here all command names
676
+ // <- TODO: Add more like 'date', 'modelName',...
677
+ // <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
678
+ ],
679
+ });
680
+ /**
681
+ * Note: [💞] Ignore a discrepancy between file name and entity name
682
+ */
683
+
684
+ /**
685
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
686
+ *
687
+ * @public exported from `@promptbook/core`
688
+ */
689
+ var ParseError = /** @class */ (function (_super) {
690
+ __extends(ParseError, _super);
691
+ function ParseError(message) {
692
+ var _this = _super.call(this, message) || this;
693
+ _this.name = 'ParseError';
694
+ Object.setPrototypeOf(_this, ParseError.prototype);
695
+ return _this;
748
696
  }
749
- return validatePipelineString(pipelineString);
750
- }
697
+ return ParseError;
698
+ }(Error));
751
699
  /**
752
- * @private internal utility of `pipelineJsonToString`
700
+ * TODO: Maybe split `ParseError` and `ApplyError`
753
701
  */
754
- function taskParameterJsonToString(taskParameterJson) {
755
- var name = taskParameterJson.name, description = taskParameterJson.description;
756
- var parameterString = "{".concat(name, "}");
757
- if (description) {
758
- parameterString = "".concat(parameterString, " ").concat(description);
702
+
703
+ /**
704
+ * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
705
+ *
706
+ * @public exported from `@promptbook/core`
707
+ */
708
+ var PipelineLogicError = /** @class */ (function (_super) {
709
+ __extends(PipelineLogicError, _super);
710
+ function PipelineLogicError(message) {
711
+ var _this = _super.call(this, message) || this;
712
+ _this.name = 'PipelineLogicError';
713
+ Object.setPrototypeOf(_this, PipelineLogicError.prototype);
714
+ return _this;
759
715
  }
760
- return parameterString;
761
- }
716
+ return PipelineLogicError;
717
+ }(Error));
718
+
762
719
  /**
763
- * TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
764
- * TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
765
- * TODO: [🏛] Maybe make some markdown builder
766
- * TODO: [🏛] Escape all
767
- * TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
720
+ * Tests if given string is valid semantic version
721
+ *
722
+ * Note: There are two simmilar functions:
723
+ * - `isValidSemanticVersion` which tests any semantic version
724
+ * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
725
+ *
726
+ * @public exported from `@promptbook/utils`
768
727
  */
728
+ function isValidSemanticVersion(version) {
729
+ if (typeof version !== 'string') {
730
+ return false;
731
+ }
732
+ if (version.startsWith('0.0.0')) {
733
+ return false;
734
+ }
735
+ return /^\d+\.\d+\.\d+(-\d+)?$/i.test(version);
736
+ }
769
737
 
770
738
  /**
771
- * Orders JSON object by keys
739
+ * Tests if given string is valid promptbook version
740
+ * It looks into list of known promptbook versions.
741
+ *
742
+ * @see https://www.npmjs.com/package/promptbook?activeTab=versions
743
+ * 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.
744
+ * Note: There are two simmilar functions:
745
+ * - `isValidSemanticVersion` which tests any semantic version
746
+ * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
772
747
  *
773
- * @returns The same type of object as the input re-ordered
774
748
  * @public exported from `@promptbook/utils`
775
749
  */
776
- function orderJson(options) {
777
- var value = options.value, order = options.order;
778
- var orderedValue = __assign(__assign({}, (order === undefined ? {} : Object.fromEntries(order.map(function (key) { return [key, undefined]; })))), value);
779
- return orderedValue;
750
+ function isValidPromptbookVersion(version) {
751
+ if (!isValidSemanticVersion(version)) {
752
+ return false;
753
+ }
754
+ if ( /* version === '1.0.0' || */version === '2.0.0' || version === '3.0.0') {
755
+ return false;
756
+ }
757
+ // <- TODO: [main] !!3 Check isValidPromptbookVersion against PROMPTBOOK_ENGINE_VERSIONS
758
+ return true;
780
759
  }
781
760
 
782
761
  /**
783
- * Freezes the given object and all its nested objects recursively
762
+ * Tests if given string is valid URL.
784
763
  *
785
- * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
786
- * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
764
+ * Note: Dataurl are considered perfectly valid.
765
+ * Note: There are two simmilar functions:
766
+ * - `isValidUrl` which tests any URL
767
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
787
768
  *
788
- * @returns The same object as the input, but deeply frozen
789
769
  * @public exported from `@promptbook/utils`
790
770
  */
791
- function $deepFreeze(objectValue) {
792
- var e_1, _a;
793
- if (Array.isArray(objectValue)) {
794
- return Object.freeze(objectValue.map(function (item) { return $deepFreeze(item); }));
771
+ function isValidUrl(url) {
772
+ if (typeof url !== 'string') {
773
+ return false;
795
774
  }
796
- var propertyNames = Object.getOwnPropertyNames(objectValue);
797
775
  try {
798
- for (var propertyNames_1 = __values(propertyNames), propertyNames_1_1 = propertyNames_1.next(); !propertyNames_1_1.done; propertyNames_1_1 = propertyNames_1.next()) {
799
- var propertyName = propertyNames_1_1.value;
800
- var value = objectValue[propertyName];
801
- if (value && typeof value === 'object') {
802
- $deepFreeze(value);
803
- }
776
+ if (url.startsWith('blob:')) {
777
+ url = url.replace(/^blob:/, '');
804
778
  }
805
- }
806
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
807
- finally {
808
- try {
809
- if (propertyNames_1_1 && !propertyNames_1_1.done && (_a = propertyNames_1.return)) _a.call(propertyNames_1);
779
+ var urlObject = new URL(url /* because fail is handled */);
780
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
781
+ return false;
810
782
  }
811
- finally { if (e_1) throw e_1.error; }
783
+ return true;
784
+ }
785
+ catch (error) {
786
+ return false;
812
787
  }
813
- Object.freeze(objectValue);
814
- return objectValue;
815
788
  }
816
- /**
817
- * TODO: [🧠] Is there a way how to meaningfully test this utility
818
- */
819
789
 
820
790
  /**
821
- * Make error report URL for the given error
791
+ * Tests if given string is valid pipeline URL URL.
822
792
  *
823
- * @private private within the repository
793
+ * Note: There are two simmilar functions:
794
+ * - `isValidUrl` which tests any URL
795
+ * - `isValidPipelineUrl` *(this one)* which tests just pipeline URL
796
+ *
797
+ * @public exported from `@promptbook/utils`
824
798
  */
825
- function getErrorReportUrl(error) {
826
- var report = {
827
- title: "\uD83D\uDC1C Error report from ".concat(NAME),
828
- body: spaceTrim__default["default"](function (block) { return "\n\n\n `".concat(error.name || 'Error', "` has occurred in the [").concat(NAME, "], please look into it @").concat(ADMIN_GITHUB_NAME, ".\n\n ```\n ").concat(block(error.message || '(no error message)'), "\n ```\n\n\n ## More info:\n\n - **Promptbook engine version:** ").concat(PROMPTBOOK_ENGINE_VERSION, "\n - **Book language version:** ").concat(BOOK_LANGUAGE_VERSION, "\n - **Time:** ").concat(new Date().toISOString(), "\n\n <details>\n <summary>Stack trace:</summary>\n\n ## Stack trace:\n\n ```stacktrace\n ").concat(block(error.stack || '(empty)'), "\n ```\n </details>\n\n "); }),
829
- };
830
- var reportUrl = new URL("https://github.com/webgptorg/promptbook/issues/new");
831
- reportUrl.searchParams.set('labels', 'bug');
832
- reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
833
- reportUrl.searchParams.set('title', report.title);
834
- reportUrl.searchParams.set('body', report.body);
835
- return reportUrl;
799
+ function isValidPipelineUrl(url) {
800
+ if (!isValidUrl(url)) {
801
+ return false;
802
+ }
803
+ if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
804
+ return false;
805
+ }
806
+ if (url.includes('#')) {
807
+ // TODO: [🐠]
808
+ return false;
809
+ }
810
+ /*
811
+ Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
812
+ if (isUrlOnPrivateNetwork(url)) {
813
+ return false;
814
+ }
815
+ */
816
+ return true;
836
817
  }
837
-
838
818
  /**
839
- * This error type indicates that the error should not happen and its last check before crashing with some other error
840
- *
841
- * @public exported from `@promptbook/core`
819
+ * TODO: [🐠] Maybe more info why the URL is invalid
842
820
  */
843
- var UnexpectedError = /** @class */ (function (_super) {
844
- __extends(UnexpectedError, _super);
845
- function UnexpectedError(message) {
846
- var _this = _super.call(this, spaceTrim.spaceTrim(function (block) { return "\n ".concat(block(message), "\n\n Note: This error should not happen.\n It's probbably a bug in the pipeline collection\n\n Please report issue:\n ").concat(block(getErrorReportUrl(new Error(message)).href), "\n\n Or contact us on ").concat(ADMIN_EMAIL, "\n\n "); })) || this;
847
- _this.name = 'UnexpectedError';
848
- Object.setPrototypeOf(_this, UnexpectedError.prototype);
849
- return _this;
850
- }
851
- return UnexpectedError;
852
- }(Error));
853
821
 
854
822
  /**
855
- * Checks if the value is [🚉] serializable as JSON
856
- * If not, throws an UnexpectedError with a rich error message and tracking
823
+ * Validates PipelineJson if it is logically valid
857
824
  *
858
- * - Almost all primitives are serializable BUT:
859
- * - `undefined` is not serializable
860
- * - `NaN` is not serializable
861
- * - Objects and arrays are serializable if all their properties are serializable
862
- * - Functions are not serializable
863
- * - Circular references are not serializable
864
- * - `Date` objects are not serializable
865
- * - `Map` and `Set` objects are not serializable
866
- * - `RegExp` objects are not serializable
867
- * - `Error` objects are not serializable
868
- * - `Symbol` objects are not serializable
869
- * - And much more...
825
+ * It checks:
826
+ * - if it has correct parameters dependency
870
827
  *
871
- * @throws UnexpectedError if the value is not serializable as JSON
872
- * @public exported from `@promptbook/utils`
828
+ * It does NOT check:
829
+ * - if it is valid json
830
+ * - if it is meaningful
831
+ *
832
+ * @param pipeline valid or invalid PipelineJson
833
+ * @returns the same pipeline if it is logically valid
834
+ * @throws {PipelineLogicError} on logical error in the pipeline
835
+ * @public exported from `@promptbook/core`
873
836
  */
874
- function checkSerializableAsJson(options) {
875
- var e_1, _a;
876
- var value = options.value, name = options.name, message = options.message;
877
- if (value === undefined) {
878
- throw new UnexpectedError("".concat(name, " is undefined"));
879
- }
880
- else if (value === null) {
881
- return;
837
+ function validatePipeline(pipeline) {
838
+ if (IS_PIPELINE_LOGIC_VALIDATED) {
839
+ validatePipeline_InnerFunction(pipeline);
882
840
  }
883
- else if (typeof value === 'boolean') {
884
- return;
841
+ else {
842
+ try {
843
+ validatePipeline_InnerFunction(pipeline);
844
+ }
845
+ catch (error) {
846
+ if (!(error instanceof PipelineLogicError)) {
847
+ throw error;
848
+ }
849
+ console.error(spaceTrim.spaceTrim(function (block) { return "\n Pipeline is not valid but logic errors are temporarily disabled via `IS_PIPELINE_LOGIC_VALIDATED`\n\n ".concat(block(error.message), "\n "); }));
850
+ }
885
851
  }
886
- else if (typeof value === 'number' && !isNaN(value)) {
887
- return;
852
+ return pipeline;
853
+ }
854
+ /**
855
+ * @private internal function for `validatePipeline`
856
+ */
857
+ function validatePipeline_InnerFunction(pipeline) {
858
+ // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
859
+ var e_1, _a, e_2, _b, e_3, _c;
860
+ var pipelineIdentification = (function () {
861
+ // Note: This is a 😐 implementation of [🚞]
862
+ var _ = [];
863
+ if (pipeline.sourceFile !== undefined) {
864
+ _.push("File: ".concat(pipeline.sourceFile));
865
+ }
866
+ if (pipeline.pipelineUrl !== undefined) {
867
+ _.push("Url: ".concat(pipeline.pipelineUrl));
868
+ }
869
+ return _.join('\n');
870
+ })();
871
+ if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
872
+ // <- Note: [🚲]
873
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Invalid promptbook URL \"".concat(pipeline.pipelineUrl, "\"\n\n ").concat(block(pipelineIdentification), "\n "); }));
888
874
  }
889
- else if (typeof value === 'string') {
890
- return;
875
+ if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
876
+ // <- Note: [🚲]
877
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Invalid Promptbook Version \"".concat(pipeline.bookVersion, "\"\n\n ").concat(block(pipelineIdentification), "\n "); }));
891
878
  }
892
- else if (typeof value === 'symbol') {
893
- throw new UnexpectedError("".concat(name, " is symbol"));
879
+ // TODO: [🧠] Maybe do here some propper JSON-schema / ZOD checking
880
+ if (!Array.isArray(pipeline.parameters)) {
881
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
882
+ throw new ParseError(spaceTrim.spaceTrim(function (block) { return "\n Pipeline is valid JSON but with wrong structure\n\n `PipelineJson.parameters` expected to be an array, but got ".concat(typeof pipeline.parameters, "\n\n ").concat(block(pipelineIdentification), "\n "); }));
894
883
  }
895
- else if (typeof value === 'function') {
896
- throw new UnexpectedError("".concat(name, " is function"));
884
+ // TODO: [🧠] Maybe do here some propper JSON-schema / ZOD checking
885
+ if (!Array.isArray(pipeline.tasks)) {
886
+ // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
887
+ throw new ParseError(spaceTrim.spaceTrim(function (block) { return "\n Pipeline is valid JSON but with wrong structure\n\n `PipelineJson.tasks` expected to be an array, but got ".concat(typeof pipeline.tasks, "\n\n ").concat(block(pipelineIdentification), "\n "); }));
897
888
  }
898
- else if (typeof value === 'object' && Array.isArray(value)) {
899
- for (var i = 0; i < value.length; i++) {
900
- checkSerializableAsJson({ name: "".concat(name, "[").concat(i, "]"), value: value[i], message: message });
889
+ var _loop_1 = function (parameter) {
890
+ if (parameter.isInput && parameter.isOutput) {
891
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n\n Parameter `{".concat(parameter.name, "}` can not be both input and output\n\n ").concat(block(pipelineIdentification), "\n "); }));
901
892
  }
902
- }
903
- else if (typeof value === 'object') {
904
- if (value instanceof Date) {
905
- throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is Date\n\n Use `string_date_iso8601` instead\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n "); }));
893
+ // Note: Testing that parameter is either intermediate or output BUT not created and unused
894
+ if (!parameter.isInput &&
895
+ !parameter.isOutput &&
896
+ !pipeline.tasks.some(function (task) { return task.dependentParameterNames.includes(parameter.name); })) {
897
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(parameter.name, "}` is created but not used\n\n You can declare {").concat(parameter.name, "} as output parameter by adding in the header:\n - OUTPUT PARAMETER `{").concat(parameter.name, "}` ").concat(parameter.description || '', "\n\n ").concat(block(pipelineIdentification), "\n\n "); }));
906
898
  }
907
- else if (value instanceof Map) {
908
- throw new UnexpectedError("".concat(name, " is Map"));
899
+ // Note: Testing that parameter is either input or result of some task
900
+ if (!parameter.isInput && !pipeline.tasks.some(function (task) { return task.resultingParameterName === parameter.name; })) {
901
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(parameter.name, "}` is declared but not defined\n\n You can do one of these:\n 1) Remove declaration of `{").concat(parameter.name, "}`\n 2) Add task that results in `-> {").concat(parameter.name, "}`\n\n ").concat(block(pipelineIdentification), "\n "); }));
909
902
  }
910
- else if (value instanceof Set) {
911
- throw new UnexpectedError("".concat(name, " is Set"));
903
+ };
904
+ try {
905
+ /*
906
+ TODO: [🧠][🅾] Should be empty pipeline valid or not
907
+ // Note: Check that pipeline has some tasks
908
+ if (pipeline.tasks.length === 0) {
909
+ throw new PipelineLogicError(
910
+ spaceTrim(
911
+ (block) => `
912
+ Pipeline must have at least one task
913
+
914
+ ${block(pipelineIdentification)}
915
+ `,
916
+ ),
917
+ );
912
918
  }
913
- else if (value instanceof RegExp) {
914
- throw new UnexpectedError("".concat(name, " is RegExp"));
919
+ */
920
+ // Note: Check each parameter individually
921
+ for (var _d = __values(pipeline.parameters), _e = _d.next(); !_e.done; _e = _d.next()) {
922
+ var parameter = _e.value;
923
+ _loop_1(parameter);
915
924
  }
916
- else if (value instanceof Error) {
917
- throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is unserialized Error\n\n Use function `serializeError`\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n\n "); }));
925
+ }
926
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
927
+ finally {
928
+ try {
929
+ if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
918
930
  }
919
- else {
931
+ finally { if (e_1) throw e_1.error; }
932
+ }
933
+ // Note: All input parameters are defined - so that they can be used as result of some task
934
+ var definedParameters = new Set(pipeline.parameters.filter(function (_a) {
935
+ var isInput = _a.isInput;
936
+ return isInput;
937
+ }).map(function (_a) {
938
+ var name = _a.name;
939
+ return name;
940
+ }));
941
+ var _loop_2 = function (task) {
942
+ var e_4, _h, e_5, _j;
943
+ if (definedParameters.has(task.resultingParameterName)) {
944
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(task.resultingParameterName, "}` is defined multiple times\n\n ").concat(block(pipelineIdentification), "\n "); }));
945
+ }
946
+ if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
947
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter name {".concat(task.resultingParameterName, "} is reserved, please use different name\n\n ").concat(block(pipelineIdentification), "\n "); }));
948
+ }
949
+ definedParameters.add(task.resultingParameterName);
950
+ if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
951
+ if (!task.format &&
952
+ !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
953
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Joker parameters are used for {".concat(task.resultingParameterName, "} but no expectations are defined\n\n ").concat(block(pipelineIdentification), "\n "); }));
954
+ }
955
+ var _loop_4 = function (joker) {
956
+ if (!task.dependentParameterNames.includes(joker)) {
957
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(joker, "}` is used for {").concat(task.resultingParameterName, "} as joker but not in `dependentParameterNames`\n\n ").concat(block(pipelineIdentification), "\n "); }));
958
+ }
959
+ };
920
960
  try {
921
- for (var _b = __values(Object.entries(value)), _c = _b.next(); !_c.done; _c = _b.next()) {
922
- var _d = __read(_c.value, 2), subName = _d[0], subValue = _d[1];
923
- if (subValue === undefined) {
924
- // Note: undefined in object is serializable - it is just omited
925
- continue;
926
- }
927
- checkSerializableAsJson({ name: "".concat(name, ".").concat(subName), value: subValue, message: message });
961
+ for (var _k = (e_4 = void 0, __values(task.jokerParameterNames)), _l = _k.next(); !_l.done; _l = _k.next()) {
962
+ var joker = _l.value;
963
+ _loop_4(joker);
928
964
  }
929
965
  }
930
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
966
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
931
967
  finally {
932
968
  try {
933
- if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
969
+ if (_l && !_l.done && (_h = _k.return)) _h.call(_k);
934
970
  }
935
- finally { if (e_1) throw e_1.error; }
971
+ finally { if (e_4) throw e_4.error; }
936
972
  }
973
+ }
974
+ if (task.expectations) {
975
+ var _loop_5 = function (unit, min, max) {
976
+ if (min !== undefined && max !== undefined && min > max) {
977
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Min expectation (=".concat(min, ") of ").concat(unit, " is higher than max expectation (=").concat(max, ")\n\n ").concat(block(pipelineIdentification), "\n "); }));
978
+ }
979
+ if (min !== undefined && min < 0) {
980
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Min expectation of ".concat(unit, " must be zero or positive\n\n ").concat(block(pipelineIdentification), "\n "); }));
981
+ }
982
+ if (max !== undefined && max <= 0) {
983
+ throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Max expectation of ".concat(unit, " must be positive\n\n ").concat(block(pipelineIdentification), "\n "); }));
984
+ }
985
+ };
937
986
  try {
938
- JSON.stringify(value); // <- TODO: [0]
939
- }
940
- catch (error) {
941
- if (!(error instanceof Error)) {
942
- throw error;
987
+ for (var _m = (e_5 = void 0, __values(Object.entries(task.expectations))), _o = _m.next(); !_o.done; _o = _m.next()) {
988
+ var _p = __read(_o.value, 2), unit = _p[0], _q = _p[1], min = _q.min, max = _q.max;
989
+ _loop_5(unit, min, max);
943
990
  }
944
- throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is not serializable\n\n ").concat(block(error.stack || error.message), "\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n "); }));
945
991
  }
946
- /*
947
- TODO: [0] Is there some more elegant way to check circular references?
948
- const seen = new Set();
949
- const stack = [{ value }];
950
- while (stack.length > 0) {
951
- const { value } = stack.pop()!;
952
- if (typeof value === 'object' && value !== null) {
953
- if (seen.has(value)) {
954
- throw new UnexpectedError(`${name} has circular reference`);
955
- }
956
- seen.add(value);
957
- if (Array.isArray(value)) {
958
- stack.push(...value.map((value) => ({ value })));
959
- } else {
960
- stack.push(...Object.values(value).map((value) => ({ value })));
961
- }
992
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
993
+ finally {
994
+ try {
995
+ if (_o && !_o.done && (_j = _m.return)) _j.call(_m);
962
996
  }
997
+ finally { if (e_5) throw e_5.error; }
963
998
  }
964
- */
965
- return;
999
+ }
1000
+ };
1001
+ try {
1002
+ // Note: Checking each task individually
1003
+ for (var _f = __values(pipeline.tasks), _g = _f.next(); !_g.done; _g = _f.next()) {
1004
+ var task = _g.value;
1005
+ _loop_2(task);
966
1006
  }
967
1007
  }
968
- else {
969
- throw new UnexpectedError(spaceTrim__default["default"](function (block) { return "\n `".concat(name, "` is unknown type\n\n Additional message for `").concat(name, "`:\n ").concat(block(message || '(nothing)'), "\n "); }));
1008
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
1009
+ finally {
1010
+ try {
1011
+ if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
1012
+ }
1013
+ finally { if (e_2) throw e_2.error; }
1014
+ }
1015
+ // Note: Detect circular dependencies
1016
+ var resovedParameters = pipeline.parameters
1017
+ .filter(function (_a) {
1018
+ var isInput = _a.isInput;
1019
+ return isInput;
1020
+ })
1021
+ .map(function (_a) {
1022
+ var name = _a.name;
1023
+ return name;
1024
+ });
1025
+ try {
1026
+ // Note: All reserved parameters are resolved
1027
+ for (var RESERVED_PARAMETER_NAMES_1 = __values(RESERVED_PARAMETER_NAMES), RESERVED_PARAMETER_NAMES_1_1 = RESERVED_PARAMETER_NAMES_1.next(); !RESERVED_PARAMETER_NAMES_1_1.done; RESERVED_PARAMETER_NAMES_1_1 = RESERVED_PARAMETER_NAMES_1.next()) {
1028
+ var reservedParameterName = RESERVED_PARAMETER_NAMES_1_1.value;
1029
+ resovedParameters = __spreadArray(__spreadArray([], __read(resovedParameters), false), [reservedParameterName], false);
1030
+ }
1031
+ }
1032
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
1033
+ finally {
1034
+ try {
1035
+ if (RESERVED_PARAMETER_NAMES_1_1 && !RESERVED_PARAMETER_NAMES_1_1.done && (_c = RESERVED_PARAMETER_NAMES_1.return)) _c.call(RESERVED_PARAMETER_NAMES_1);
1036
+ }
1037
+ finally { if (e_3) throw e_3.error; }
1038
+ }
1039
+ var unresovedTasks = __spreadArray([], __read(pipeline.tasks), false);
1040
+ var loopLimit = LOOP_LIMIT;
1041
+ var _loop_3 = function () {
1042
+ if (loopLimit-- < 0) {
1043
+ // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
1044
+ throw new UnexpectedError(spaceTrim.spaceTrim(function (block) { return "\n Loop limit reached during detection of circular dependencies in `validatePipeline`\n\n ".concat(block(pipelineIdentification), "\n "); }));
1045
+ }
1046
+ var currentlyResovedTasks = unresovedTasks.filter(function (task) {
1047
+ return task.dependentParameterNames.every(function (name) { return resovedParameters.includes(name); });
1048
+ });
1049
+ if (currentlyResovedTasks.length === 0) {
1050
+ throw new PipelineLogicError(
1051
+ // TODO: [🐎] DRY
1052
+ spaceTrim.spaceTrim(function (block) { return "\n\n Can not resolve some parameters:\n Either you are using a parameter that is not defined, or there are some circular dependencies.\n\n ".concat(block(pipelineIdentification), "\n\n **Can not resolve:**\n ").concat(block(unresovedTasks
1053
+ .map(function (_a) {
1054
+ var resultingParameterName = _a.resultingParameterName, dependentParameterNames = _a.dependentParameterNames;
1055
+ return "- Parameter `{".concat(resultingParameterName, "}` which depends on ").concat(dependentParameterNames
1056
+ .map(function (dependentParameterName) { return "`{".concat(dependentParameterName, "}`"); })
1057
+ .join(' and '));
1058
+ })
1059
+ .join('\n')), "\n\n **Resolved:**\n ").concat(block(resovedParameters
1060
+ .filter(function (name) {
1061
+ return !RESERVED_PARAMETER_NAMES.includes(name);
1062
+ })
1063
+ .map(function (name) { return "- Parameter `{".concat(name, "}`"); })
1064
+ .join('\n')), "\n\n\n **Reserved (which are available):**\n ").concat(block(resovedParameters
1065
+ .filter(function (name) {
1066
+ return RESERVED_PARAMETER_NAMES.includes(name);
1067
+ })
1068
+ .map(function (name) { return "- Parameter `{".concat(name, "}`"); })
1069
+ .join('\n')), "\n\n\n "); }));
1070
+ }
1071
+ resovedParameters = __spreadArray(__spreadArray([], __read(resovedParameters), false), __read(currentlyResovedTasks.map(function (_a) {
1072
+ var resultingParameterName = _a.resultingParameterName;
1073
+ return resultingParameterName;
1074
+ })), false);
1075
+ unresovedTasks = unresovedTasks.filter(function (task) { return !currentlyResovedTasks.includes(task); });
1076
+ };
1077
+ while (unresovedTasks.length > 0) {
1078
+ _loop_3();
970
1079
  }
1080
+ // Note: Check that formfactor is corresponding to the pipeline interface
1081
+ // TODO: !!6 Implement this
1082
+ // pipeline.formfactorName
971
1083
  }
972
1084
  /**
973
- * TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
974
- * TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
975
- * Note: [🐠] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
976
- */
977
-
978
- /**
979
- * @@@
980
- *
981
- * @public exported from `@promptbook/utils`
1085
+ * TODO: [🧞‍♀️] Do not allow joker + foreach
1086
+ * TODO: [🧠] Work with promptbookVersion
1087
+ * TODO: Use here some json-schema, Zod or something similar and change it to:
1088
+ * > /**
1089
+ * > * Validates PipelineJson if it is logically valid.
1090
+ * > *
1091
+ * > * It checks:
1092
+ * > * - it has a valid structure
1093
+ * > * - ...
1094
+ * > ex port function validatePipeline(promptbook: really_unknown): asserts promptbook is PipelineJson {
982
1095
  */
983
- function deepClone(objectValue) {
984
- return JSON.parse(JSON.stringify(objectValue));
985
- /*
986
- TODO: [🧠] Is there a better implementation?
987
- > const propertyNames = Object.getOwnPropertyNames(objectValue);
988
- > for (const propertyName of propertyNames) {
989
- > const value = (objectValue as really_any)[propertyName];
990
- > if (value && typeof value === 'object') {
991
- > deepClone(value);
992
- > }
993
- > }
994
- > return Object.assign({}, objectValue);
995
- */
996
- }
997
1096
  /**
998
- * TODO: [🧠] Is there a way how to meaningfully test this utility
1097
+ * TODO: [🧳][main] !!4 Validate that all examples match expectations
1098
+ * TODO: [🧳][🐝][main] !!4 Validate that knowledge is valid (non-void)
1099
+ * TODO: [🧳][main] !!4 Validate that persona can be used only with CHAT variant
1100
+ * TODO: [🧳][main] !!4 Validate that parameter with reserved name not used RESERVED_PARAMETER_NAMES
1101
+ * TODO: [🧳][main] !!4 Validate that reserved parameter is not used as joker
1102
+ * TODO: [🧠] Validation not only logic itself but imports around - files and websites and rerefenced pipelines exists
1103
+ * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
999
1104
  */
1000
1105
 
1001
1106
  /**
1002
- * Utility to export a JSON object from a function
1003
- *
1004
- * 1) Checks if the value is serializable as JSON
1005
- * 2) Makes a deep clone of the object
1006
- * 2) Orders the object properties
1007
- * 2) Deeply freezes the cloned object
1107
+ * Loads the books from the archive file with `.bookc` extension
1008
1108
  *
1009
- * Note: This function does not mutates the given object
1109
+ * @param filePath Path to the archive file with `.bookc` extension
1110
+ * @param fs Filesystem tools
1111
+ * @returns Pipelines loaded from the archive
1010
1112
  *
1011
- * @returns The same type of object as the input but read-only and re-ordered
1012
- * @public exported from `@promptbook/utils`
1113
+ * @private utility of Prompbook
1013
1114
  */
1014
- function exportJson(options) {
1015
- var name = options.name, value = options.value, order = options.order, message = options.message;
1016
- checkSerializableAsJson({ name: name, value: value, message: message });
1017
- var orderedValue =
1018
- // TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
1019
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1020
- // @ts-ignore
1021
- order === undefined
1022
- ? deepClone(value)
1023
- : orderJson({
1024
- value: value,
1025
- // <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
1026
- order: order,
1115
+ function loadArchive(filePath, fs) {
1116
+ return __awaiter(this, void 0, void 0, function () {
1117
+ var data, archive, indexFile, collectionJson, _a, _b, collectionJson_1, collectionJson_1_1, pipeline;
1118
+ var e_1, _c;
1119
+ return __generator(this, function (_d) {
1120
+ switch (_d.label) {
1121
+ case 0:
1122
+ if (!filePath.endsWith('.bookc')) {
1123
+ throw new UnexpectedError("Archive file must have '.bookc' extension");
1124
+ }
1125
+ return [4 /*yield*/, fs.readFile(filePath)];
1126
+ case 1:
1127
+ data = _d.sent();
1128
+ return [4 /*yield*/, JSZip__default["default"].loadAsync(data)];
1129
+ case 2:
1130
+ archive = _d.sent();
1131
+ indexFile = archive.file('index.book.json');
1132
+ if (!indexFile) {
1133
+ throw new UnexpectedError("Archive does not contain 'index.book.json' file");
1134
+ }
1135
+ _b = (_a = JSON).parse;
1136
+ return [4 /*yield*/, indexFile.async('text')];
1137
+ case 3:
1138
+ collectionJson = _b.apply(_a, [_d.sent()]);
1139
+ try {
1140
+ for (collectionJson_1 = __values(collectionJson), collectionJson_1_1 = collectionJson_1.next(); !collectionJson_1_1.done; collectionJson_1_1 = collectionJson_1.next()) {
1141
+ pipeline = collectionJson_1_1.value;
1142
+ validatePipeline(pipeline);
1143
+ }
1144
+ }
1145
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
1146
+ finally {
1147
+ try {
1148
+ if (collectionJson_1_1 && !collectionJson_1_1.done && (_c = collectionJson_1.return)) _c.call(collectionJson_1);
1149
+ }
1150
+ finally { if (e_1) throw e_1.error; }
1151
+ }
1152
+ return [2 /*return*/, collectionJson];
1153
+ }
1027
1154
  });
1028
- $deepFreeze(orderedValue);
1029
- return orderedValue;
1155
+ });
1030
1156
  }
1031
1157
  /**
1032
- * TODO: [🧠] Is there a way how to meaningfully test this utility
1158
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
1033
1159
  */
1034
1160
 
1035
- /**
1036
- * Order of keys in the pipeline JSON
1037
- *
1038
- * @public exported from `@promptbook/core`
1039
- */
1040
- var ORDER_OF_PIPELINE_JSON = [
1041
- // Note: [🍙] In this order will be pipeline serialized
1042
- 'title',
1043
- 'pipelineUrl',
1044
- 'bookVersion',
1045
- 'description',
1046
- 'formfactorName',
1047
- 'parameters',
1048
- 'tasks',
1049
- 'personas',
1050
- 'preparations',
1051
- 'knowledgeSources',
1052
- 'knowledgePieces',
1053
- 'sources', // <- TODO: [🧠] Where should the `sources` be
1054
- ];
1055
- /**
1056
- * Nonce which is used for replacing things in strings
1057
- *
1058
- * @private within the repository
1059
- */
1060
- var REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
1061
- /**
1062
- * @@@
1063
- *
1064
- * @private within the repository
1065
- */
1066
- var RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
1067
- /**
1068
- * @@@
1069
- *
1070
- * @private within the repository
1071
- */
1072
- var RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
1073
- /**
1074
- * The names of the parameters that are reserved for special purposes
1075
- *
1076
- * @public exported from `@promptbook/core`
1077
- */
1078
- var RESERVED_PARAMETER_NAMES = exportJson({
1079
- name: 'RESERVED_PARAMETER_NAMES',
1080
- message: "The names of the parameters that are reserved for special purposes",
1081
- value: [
1082
- 'content',
1083
- 'context',
1084
- 'knowledge',
1085
- 'examples',
1086
- 'modelName',
1087
- 'currentDate',
1088
- // <- TODO: list here all command names
1089
- // <- TODO: Add more like 'date', 'modelName',...
1090
- // <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
1091
- ],
1092
- });
1093
- /**
1094
- * Note: [💞] Ignore a discrepancy between file name and entity name
1095
- */
1161
+ 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"}];
1096
1162
 
1097
1163
  /**
1098
- * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
1164
+ * Checks if value is valid email
1099
1165
  *
1100
- * @public exported from `@promptbook/core`
1166
+ * @public exported from `@promptbook/utils`
1101
1167
  */
1102
- var PipelineLogicError = /** @class */ (function (_super) {
1103
- __extends(PipelineLogicError, _super);
1104
- function PipelineLogicError(message) {
1105
- var _this = _super.call(this, message) || this;
1106
- _this.name = 'PipelineLogicError';
1107
- Object.setPrototypeOf(_this, PipelineLogicError.prototype);
1108
- return _this;
1168
+ function isValidEmail(email) {
1169
+ if (typeof email !== 'string') {
1170
+ return false;
1109
1171
  }
1110
- return PipelineLogicError;
1111
- }(Error));
1172
+ if (email.split('\n').length > 1) {
1173
+ return false;
1174
+ }
1175
+ return /^.+@.+\..+$/.test(email);
1176
+ }
1112
1177
 
1113
1178
  /**
1114
- * Tests if given string is valid semantic version
1115
- *
1116
- * Note: There are two simmilar functions:
1117
- * - `isValidSemanticVersion` which tests any semantic version
1118
- * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
1179
+ * Tests if given string is valid URL.
1119
1180
  *
1181
+ * Note: This does not check if the file exists only if the path is valid
1120
1182
  * @public exported from `@promptbook/utils`
1121
1183
  */
1122
- function isValidSemanticVersion(version) {
1123
- if (typeof version !== 'string') {
1184
+ function isValidFilePath(filename) {
1185
+ if (typeof filename !== 'string') {
1124
1186
  return false;
1125
1187
  }
1126
- if (version.startsWith('0.0.0')) {
1188
+ if (filename.split('\n').length > 1) {
1127
1189
  return false;
1128
1190
  }
1129
- return /^\d+\.\d+\.\d+(-\d+)?$/i.test(version);
1191
+ if (filename.split(' ').length >
1192
+ 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
1193
+ return false;
1194
+ }
1195
+ var filenameSlashes = filename.split('\\').join('/');
1196
+ // Absolute Unix path: /hello.txt
1197
+ if (/^(\/)/i.test(filenameSlashes)) {
1198
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
1199
+ return true;
1200
+ }
1201
+ // Absolute Windows path: /hello.txt
1202
+ if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
1203
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
1204
+ return true;
1205
+ }
1206
+ // Relative path: ./hello.txt
1207
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
1208
+ // console.log(filename, 'Relative path: ./hello.txt');
1209
+ return true;
1210
+ }
1211
+ // Allow paths like foo/hello
1212
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
1213
+ // console.log(filename, 'Allow paths like foo/hello');
1214
+ return true;
1215
+ }
1216
+ // Allow paths like hello.book
1217
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
1218
+ // console.log(filename, 'Allow paths like hello.book');
1219
+ return true;
1220
+ }
1221
+ return false;
1130
1222
  }
1223
+ /**
1224
+ * TODO: [🍏] Implement for MacOs
1225
+ */
1131
1226
 
1132
1227
  /**
1133
- * Tests if given string is valid promptbook version
1134
- * It looks into list of known promptbook versions.
1135
- *
1136
- * @see https://www.npmjs.com/package/promptbook?activeTab=versions
1137
- * 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.
1138
- * Note: There are two simmilar functions:
1139
- * - `isValidSemanticVersion` which tests any semantic version
1140
- * - `isValidPromptbookVersion` *(this one)* which tests just Promptbook versions
1228
+ * Function isValidJsonString will tell you if the string is valid JSON or not
1141
1229
  *
1142
1230
  * @public exported from `@promptbook/utils`
1143
1231
  */
1144
- function isValidPromptbookVersion(version) {
1145
- if (!isValidSemanticVersion(version)) {
1146
- return false;
1232
+ function isValidJsonString(value /* <- [👨‍⚖️] */) {
1233
+ try {
1234
+ JSON.parse(value);
1235
+ return true;
1147
1236
  }
1148
- if ( /* version === '1.0.0' || */version === '2.0.0' || version === '3.0.0') {
1237
+ catch (error) {
1238
+ if (!(error instanceof Error)) {
1239
+ throw error;
1240
+ }
1241
+ if (error.message.includes('Unexpected token')) {
1242
+ return false;
1243
+ }
1149
1244
  return false;
1150
1245
  }
1151
- // <- TODO: [main] !!3 Check isValidPromptbookVersion against PROMPTBOOK_ENGINE_VERSIONS
1152
- return true;
1153
1246
  }
1154
-
1155
- /**
1156
- * Tests if given string is valid pipeline URL URL.
1157
- *
1158
- * Note: There are two simmilar functions:
1159
- * - `isValidUrl` which tests any URL
1160
- * - `isValidPipelineUrl` *(this one)* which tests just pipeline URL
1247
+
1248
+ /**
1249
+ * Function `validatePipelineString` will validate the if the string is a valid pipeline string
1250
+ * 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.
1161
1251
  *
1162
- * @public exported from `@promptbook/utils`
1252
+ * @param {string} pipelineString the candidate for a pipeline string
1253
+ * @returns {PipelineString} the same string as input, but validated as valid
1254
+ * @throws {ParseError} if the string is not a valid pipeline string
1255
+ * @public exported from `@promptbook/core`
1163
1256
  */
1164
- function isValidPipelineUrl(url) {
1165
- if (!isValidUrl(url)) {
1166
- return false;
1257
+ function validatePipelineString(pipelineString) {
1258
+ if (isValidJsonString(pipelineString)) {
1259
+ throw new ParseError('Expected a book, but got a JSON string');
1167
1260
  }
1168
- if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
1169
- return false;
1261
+ else if (isValidUrl(pipelineString)) {
1262
+ throw new ParseError("Expected a book, but got just the URL \"".concat(pipelineString, "\""));
1170
1263
  }
1171
- if (url.includes('#')) {
1172
- // TODO: [🐠]
1173
- return false;
1264
+ else if (isValidFilePath(pipelineString)) {
1265
+ throw new ParseError("Expected a book, but got just the file path \"".concat(pipelineString, "\""));
1174
1266
  }
1175
- /*
1176
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
1177
- if (isUrlOnPrivateNetwork(url)) {
1178
- return false;
1267
+ else if (isValidEmail(pipelineString)) {
1268
+ throw new ParseError("Expected a book, but got just the email \"".concat(pipelineString, "\""));
1179
1269
  }
1180
- */
1181
- return true;
1270
+ // <- TODO: Implement the validation + add tests when the pipeline logic considered as invalid
1271
+ return pipelineString;
1182
1272
  }
1183
1273
  /**
1184
- * TODO: [🐠] Maybe more info why the URL is invalid
1274
+ * TODO: [🧠][🈴] Where is the best location for this file
1185
1275
  */
1186
1276
 
1187
1277
  /**
1188
- * Validates PipelineJson if it is logically valid
1189
- *
1190
- * It checks:
1191
- * - if it has correct parameters dependency
1192
- *
1193
- * It does NOT check:
1194
- * - if it is valid json
1195
- * - if it is meaningful
1278
+ * Prettify the html code
1196
1279
  *
1197
- * @param pipeline valid or invalid PipelineJson
1198
- * @returns the same pipeline if it is logically valid
1199
- * @throws {PipelineLogicError} on logical error in the pipeline
1200
- * @public exported from `@promptbook/core`
1280
+ * @param content raw html code
1281
+ * @returns formatted html code
1282
+ * @private withing the package because of HUGE size of prettier dependency
1201
1283
  */
1202
- function validatePipeline(pipeline) {
1203
- if (IS_PIPELINE_LOGIC_VALIDATED) {
1204
- validatePipeline_InnerFunction(pipeline);
1284
+ function prettifyMarkdown(content) {
1285
+ try {
1286
+ return prettier.format(content, {
1287
+ parser: 'markdown',
1288
+ plugins: [parserHtml__default["default"]],
1289
+ // TODO: DRY - make some import or auto-copy of .prettierrc
1290
+ endOfLine: 'lf',
1291
+ tabWidth: 4,
1292
+ singleQuote: true,
1293
+ trailingComma: 'all',
1294
+ arrowParens: 'always',
1295
+ printWidth: 120,
1296
+ htmlWhitespaceSensitivity: 'ignore',
1297
+ jsxBracketSameLine: false,
1298
+ bracketSpacing: true,
1299
+ });
1205
1300
  }
1206
- else {
1207
- try {
1208
- validatePipeline_InnerFunction(pipeline);
1209
- }
1210
- catch (error) {
1211
- if (!(error instanceof PipelineLogicError)) {
1212
- throw error;
1213
- }
1214
- console.error(spaceTrim.spaceTrim(function (block) { return "\n Pipeline is not valid but logic errors are temporarily disabled via `IS_PIPELINE_LOGIC_VALIDATED`\n\n ".concat(block(error.message), "\n "); }));
1215
- }
1301
+ catch (error) {
1302
+ // TODO: [🟥] Detect browser / node and make it colorfull
1303
+ console.error('There was an error with prettifying the markdown, using the original as the fallback', {
1304
+ error: error,
1305
+ html: content,
1306
+ });
1307
+ return content;
1216
1308
  }
1217
- return pipeline;
1218
1309
  }
1310
+
1219
1311
  /**
1220
- * @private internal function for `validatePipeline`
1312
+ * Makes first letter of a string uppercase
1313
+ *
1314
+ * @public exported from `@promptbook/utils`
1221
1315
  */
1222
- function validatePipeline_InnerFunction(pipeline) {
1223
- // TODO: [🧠] Maybe test if promptbook is a promise and make specific error case for that
1224
- var e_1, _a, e_2, _b, e_3, _c;
1225
- var pipelineIdentification = (function () {
1226
- // Note: This is a 😐 implementation of [🚞]
1227
- var _ = [];
1228
- if (pipeline.sourceFile !== undefined) {
1229
- _.push("File: ".concat(pipeline.sourceFile));
1230
- }
1231
- if (pipeline.pipelineUrl !== undefined) {
1232
- _.push("Url: ".concat(pipeline.pipelineUrl));
1233
- }
1234
- return _.join('\n');
1235
- })();
1236
- if (pipeline.pipelineUrl !== undefined && !isValidPipelineUrl(pipeline.pipelineUrl)) {
1237
- // <- Note: [🚲]
1238
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Invalid promptbook URL \"".concat(pipeline.pipelineUrl, "\"\n\n ").concat(block(pipelineIdentification), "\n "); }));
1239
- }
1240
- if (pipeline.bookVersion !== undefined && !isValidPromptbookVersion(pipeline.bookVersion)) {
1241
- // <- Note: [🚲]
1242
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Invalid Promptbook Version \"".concat(pipeline.bookVersion, "\"\n\n ").concat(block(pipelineIdentification), "\n "); }));
1316
+ function capitalize(word) {
1317
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
1318
+ }
1319
+
1320
+ /**
1321
+ * Converts promptbook in JSON format to string format
1322
+ *
1323
+ * @deprecated TODO: [🥍][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1324
+ * @param pipelineJson Promptbook in JSON format (.bookc)
1325
+ * @returns Promptbook in string format (.book.md)
1326
+ * @public exported from `@promptbook/core`
1327
+ */
1328
+ function pipelineJsonToString(pipelineJson) {
1329
+ var e_1, _a, e_2, _b, e_3, _c, e_4, _d, e_5, _e, e_6, _f;
1330
+ var title = pipelineJson.title, pipelineUrl = pipelineJson.pipelineUrl, bookVersion = pipelineJson.bookVersion, description = pipelineJson.description, parameters = pipelineJson.parameters, tasks = pipelineJson.tasks;
1331
+ var pipelineString = "# ".concat(title);
1332
+ if (description) {
1333
+ pipelineString += '\n\n';
1334
+ pipelineString += description;
1243
1335
  }
1244
- // TODO: [🧠] Maybe do here some propper JSON-schema / ZOD checking
1245
- if (!Array.isArray(pipeline.parameters)) {
1246
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
1247
- throw new ParseError(spaceTrim.spaceTrim(function (block) { return "\n Pipeline is valid JSON but with wrong structure\n\n `PipelineJson.parameters` expected to be an array, but got ".concat(typeof pipeline.parameters, "\n\n ").concat(block(pipelineIdentification), "\n "); }));
1336
+ var commands = [];
1337
+ if (pipelineUrl) {
1338
+ commands.push("PIPELINE URL ".concat(pipelineUrl));
1248
1339
  }
1249
- // TODO: [🧠] Maybe do here some propper JSON-schema / ZOD checking
1250
- if (!Array.isArray(pipeline.tasks)) {
1251
- // TODO: [🧠] what is the correct error tp throw - maybe PromptbookSchemaError
1252
- throw new ParseError(spaceTrim.spaceTrim(function (block) { return "\n Pipeline is valid JSON but with wrong structure\n\n `PipelineJson.tasks` expected to be an array, but got ".concat(typeof pipeline.tasks, "\n\n ").concat(block(pipelineIdentification), "\n "); }));
1340
+ if (bookVersion !== "undefined") {
1341
+ commands.push("BOOK VERSION ".concat(bookVersion));
1253
1342
  }
1254
- var _loop_1 = function (parameter) {
1255
- if (parameter.isInput && parameter.isOutput) {
1256
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n\n Parameter `{".concat(parameter.name, "}` can not be both input and output\n\n ").concat(block(pipelineIdentification), "\n "); }));
1257
- }
1258
- // Note: Testing that parameter is either intermediate or output BUT not created and unused
1259
- if (!parameter.isInput &&
1260
- !parameter.isOutput &&
1261
- !pipeline.tasks.some(function (task) { return task.dependentParameterNames.includes(parameter.name); })) {
1262
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(parameter.name, "}` is created but not used\n\n You can declare {").concat(parameter.name, "} as output parameter by adding in the header:\n - OUTPUT PARAMETER `{").concat(parameter.name, "}` ").concat(parameter.description || '', "\n\n ").concat(block(pipelineIdentification), "\n\n "); }));
1263
- }
1264
- // Note: Testing that parameter is either input or result of some task
1265
- if (!parameter.isInput && !pipeline.tasks.some(function (task) { return task.resultingParameterName === parameter.name; })) {
1266
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(parameter.name, "}` is declared but not defined\n\n You can do one of these:\n 1) Remove declaration of `{").concat(parameter.name, "}`\n 2) Add task that results in `-> {").concat(parameter.name, "}`\n\n ").concat(block(pipelineIdentification), "\n "); }));
1267
- }
1268
- };
1343
+ // TODO: [main] !!5 This increases size of the bundle and is probbably not necessary
1344
+ pipelineString = prettifyMarkdown(pipelineString);
1269
1345
  try {
1270
- /*
1271
- TODO: [🧠][🅾] Should be empty pipeline valid or not
1272
- // Note: Check that pipeline has some tasks
1273
- if (pipeline.tasks.length === 0) {
1274
- throw new PipelineLogicError(
1275
- spaceTrim(
1276
- (block) => `
1277
- Pipeline must have at least one task
1278
-
1279
- ${block(pipelineIdentification)}
1280
- `,
1281
- ),
1282
- );
1283
- }
1284
- */
1285
- // Note: Check each parameter individually
1286
- for (var _d = __values(pipeline.parameters), _e = _d.next(); !_e.done; _e = _d.next()) {
1287
- var parameter = _e.value;
1288
- _loop_1(parameter);
1346
+ for (var _g = __values(parameters.filter(function (_a) {
1347
+ var isInput = _a.isInput;
1348
+ return isInput;
1349
+ })), _h = _g.next(); !_h.done; _h = _g.next()) {
1350
+ var parameter = _h.value;
1351
+ commands.push("INPUT PARAMETER ".concat(taskParameterJsonToString(parameter)));
1289
1352
  }
1290
1353
  }
1291
1354
  catch (e_1_1) { e_1 = { error: e_1_1 }; }
1292
1355
  finally {
1293
1356
  try {
1294
- if (_e && !_e.done && (_a = _d.return)) _a.call(_d);
1357
+ if (_h && !_h.done && (_a = _g.return)) _a.call(_g);
1295
1358
  }
1296
1359
  finally { if (e_1) throw e_1.error; }
1297
1360
  }
1298
- // Note: All input parameters are defined - so that they can be used as result of some task
1299
- var definedParameters = new Set(pipeline.parameters.filter(function (_a) {
1300
- var isInput = _a.isInput;
1301
- return isInput;
1302
- }).map(function (_a) {
1303
- var name = _a.name;
1304
- return name;
1305
- }));
1306
- var _loop_2 = function (task) {
1307
- var e_4, _h, e_5, _j;
1308
- if (definedParameters.has(task.resultingParameterName)) {
1309
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(task.resultingParameterName, "}` is defined multiple times\n\n ").concat(block(pipelineIdentification), "\n "); }));
1361
+ try {
1362
+ for (var _j = __values(parameters.filter(function (_a) {
1363
+ var isOutput = _a.isOutput;
1364
+ return isOutput;
1365
+ })), _k = _j.next(); !_k.done; _k = _j.next()) {
1366
+ var parameter = _k.value;
1367
+ commands.push("OUTPUT PARAMETER ".concat(taskParameterJsonToString(parameter)));
1310
1368
  }
1311
- if (RESERVED_PARAMETER_NAMES.includes(task.resultingParameterName)) {
1312
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter name {".concat(task.resultingParameterName, "} is reserved, please use different name\n\n ").concat(block(pipelineIdentification), "\n "); }));
1369
+ }
1370
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
1371
+ finally {
1372
+ try {
1373
+ if (_k && !_k.done && (_b = _j.return)) _b.call(_j);
1313
1374
  }
1314
- definedParameters.add(task.resultingParameterName);
1315
- if (task.jokerParameterNames && task.jokerParameterNames.length > 0) {
1316
- if (!task.format &&
1317
- !task.expectations /* <- TODO: Require at least 1 -> min <- expectation to use jokers */) {
1318
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Joker parameters are used for {".concat(task.resultingParameterName, "} but no expectations are defined\n\n ").concat(block(pipelineIdentification), "\n "); }));
1375
+ finally { if (e_2) throw e_2.error; }
1376
+ }
1377
+ pipelineString += '\n\n';
1378
+ pipelineString += commands.map(function (command) { return "- ".concat(command); }).join('\n');
1379
+ try {
1380
+ for (var tasks_1 = __values(tasks), tasks_1_1 = tasks_1.next(); !tasks_1_1.done; tasks_1_1 = tasks_1.next()) {
1381
+ var task = tasks_1_1.value;
1382
+ var
1383
+ /* Note: Not using:> name, */
1384
+ title_1 = task.title, description_1 = task.description,
1385
+ /* Note: dependentParameterNames, */
1386
+ jokers = task.jokerParameterNames, taskType = task.taskType, content = task.content, postprocessing = task.postprocessingFunctionNames, expectations = task.expectations, format = task.format, resultingParameterName = task.resultingParameterName;
1387
+ pipelineString += '\n\n';
1388
+ pipelineString += "## ".concat(title_1);
1389
+ if (description_1) {
1390
+ pipelineString += '\n\n';
1391
+ pipelineString += description_1;
1392
+ }
1393
+ var commands_1 = [];
1394
+ var contentLanguage = 'text';
1395
+ if (taskType === 'PROMPT_TASK') {
1396
+ var modelRequirements = task.modelRequirements;
1397
+ var _l = modelRequirements || {}, modelName = _l.modelName, modelVariant = _l.modelVariant;
1398
+ // Note: Do nothing, it is default
1399
+ // commands.push(`PROMPT`);
1400
+ if (modelVariant) {
1401
+ commands_1.push("MODEL VARIANT ".concat(capitalize(modelVariant)));
1402
+ }
1403
+ if (modelName) {
1404
+ commands_1.push("MODEL NAME `".concat(modelName, "`"));
1405
+ }
1406
+ }
1407
+ else if (taskType === 'SIMPLE_TASK') {
1408
+ commands_1.push("SIMPLE TEMPLATE");
1409
+ // Note: Nothing special here
1319
1410
  }
1320
- var _loop_4 = function (joker) {
1321
- if (!task.dependentParameterNames.includes(joker)) {
1322
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Parameter `{".concat(joker, "}` is used for {").concat(task.resultingParameterName, "} as joker but not in `dependentParameterNames`\n\n ").concat(block(pipelineIdentification), "\n "); }));
1411
+ else if (taskType === 'SCRIPT_TASK') {
1412
+ commands_1.push("SCRIPT");
1413
+ if (task.contentLanguage) {
1414
+ contentLanguage = task.contentLanguage;
1323
1415
  }
1324
- };
1325
- try {
1326
- for (var _k = (e_4 = void 0, __values(task.jokerParameterNames)), _l = _k.next(); !_l.done; _l = _k.next()) {
1327
- var joker = _l.value;
1328
- _loop_4(joker);
1416
+ else {
1417
+ contentLanguage = '';
1329
1418
  }
1330
1419
  }
1331
- catch (e_4_1) { e_4 = { error: e_4_1 }; }
1332
- finally {
1420
+ else if (taskType === 'DIALOG_TASK') {
1421
+ commands_1.push("DIALOG");
1422
+ // Note: Nothing special here
1423
+ } // <- }else if([🅱]
1424
+ if (jokers) {
1333
1425
  try {
1334
- if (_l && !_l.done && (_h = _k.return)) _h.call(_k);
1335
- }
1336
- finally { if (e_4) throw e_4.error; }
1337
- }
1338
- }
1339
- if (task.expectations) {
1340
- var _loop_5 = function (unit, min, max) {
1341
- if (min !== undefined && max !== undefined && min > max) {
1342
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Min expectation (=".concat(min, ") of ").concat(unit, " is higher than max expectation (=").concat(max, ")\n\n ").concat(block(pipelineIdentification), "\n "); }));
1426
+ for (var jokers_1 = (e_4 = void 0, __values(jokers)), jokers_1_1 = jokers_1.next(); !jokers_1_1.done; jokers_1_1 = jokers_1.next()) {
1427
+ var joker = jokers_1_1.value;
1428
+ commands_1.push("JOKER {".concat(joker, "}"));
1429
+ }
1343
1430
  }
1344
- if (min !== undefined && min < 0) {
1345
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Min expectation of ".concat(unit, " must be zero or positive\n\n ").concat(block(pipelineIdentification), "\n "); }));
1431
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
1432
+ finally {
1433
+ try {
1434
+ if (jokers_1_1 && !jokers_1_1.done && (_d = jokers_1.return)) _d.call(jokers_1);
1435
+ }
1436
+ finally { if (e_4) throw e_4.error; }
1346
1437
  }
1347
- if (max !== undefined && max <= 0) {
1348
- throw new PipelineLogicError(spaceTrim.spaceTrim(function (block) { return "\n Max expectation of ".concat(unit, " must be positive\n\n ").concat(block(pipelineIdentification), "\n "); }));
1438
+ } /* not else */
1439
+ if (postprocessing) {
1440
+ try {
1441
+ for (var postprocessing_1 = (e_5 = void 0, __values(postprocessing)), postprocessing_1_1 = postprocessing_1.next(); !postprocessing_1_1.done; postprocessing_1_1 = postprocessing_1.next()) {
1442
+ var postprocessingFunctionName = postprocessing_1_1.value;
1443
+ commands_1.push("POSTPROCESSING `".concat(postprocessingFunctionName, "`"));
1444
+ }
1349
1445
  }
1350
- };
1351
- try {
1352
- for (var _m = (e_5 = void 0, __values(Object.entries(task.expectations))), _o = _m.next(); !_o.done; _o = _m.next()) {
1353
- var _p = __read(_o.value, 2), unit = _p[0], _q = _p[1], min = _q.min, max = _q.max;
1354
- _loop_5(unit, min, max);
1446
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
1447
+ finally {
1448
+ try {
1449
+ if (postprocessing_1_1 && !postprocessing_1_1.done && (_e = postprocessing_1.return)) _e.call(postprocessing_1);
1450
+ }
1451
+ finally { if (e_5) throw e_5.error; }
1355
1452
  }
1356
- }
1357
- catch (e_5_1) { e_5 = { error: e_5_1 }; }
1358
- finally {
1453
+ } /* not else */
1454
+ if (expectations) {
1359
1455
  try {
1360
- if (_o && !_o.done && (_j = _m.return)) _j.call(_m);
1456
+ for (var _m = (e_6 = void 0, __values(Object.entries(expectations))), _o = _m.next(); !_o.done; _o = _m.next()) {
1457
+ var _p = __read(_o.value, 2), unit = _p[0], _q = _p[1], min = _q.min, max = _q.max;
1458
+ if (min === max) {
1459
+ commands_1.push("EXPECT EXACTLY ".concat(min, " ").concat(capitalize(unit + (min > 1 ? 's' : ''))));
1460
+ }
1461
+ else {
1462
+ if (min !== undefined) {
1463
+ commands_1.push("EXPECT MIN ".concat(min, " ").concat(capitalize(unit + (min > 1 ? 's' : ''))));
1464
+ } /* not else */
1465
+ if (max !== undefined) {
1466
+ commands_1.push("EXPECT MAX ".concat(max, " ").concat(capitalize(unit + (max > 1 ? 's' : ''))));
1467
+ }
1468
+ }
1469
+ }
1361
1470
  }
1362
- finally { if (e_5) throw e_5.error; }
1363
- }
1364
- }
1365
- };
1366
- try {
1367
- // Note: Checking each task individually
1368
- for (var _f = __values(pipeline.tasks), _g = _f.next(); !_g.done; _g = _f.next()) {
1369
- var task = _g.value;
1370
- _loop_2(task);
1371
- }
1372
- }
1373
- catch (e_2_1) { e_2 = { error: e_2_1 }; }
1374
- finally {
1375
- try {
1376
- if (_g && !_g.done && (_b = _f.return)) _b.call(_f);
1377
- }
1378
- finally { if (e_2) throw e_2.error; }
1379
- }
1380
- // Note: Detect circular dependencies
1381
- var resovedParameters = pipeline.parameters
1382
- .filter(function (_a) {
1383
- var isInput = _a.isInput;
1384
- return isInput;
1385
- })
1386
- .map(function (_a) {
1387
- var name = _a.name;
1388
- return name;
1389
- });
1390
- try {
1391
- // Note: All reserved parameters are resolved
1392
- for (var RESERVED_PARAMETER_NAMES_1 = __values(RESERVED_PARAMETER_NAMES), RESERVED_PARAMETER_NAMES_1_1 = RESERVED_PARAMETER_NAMES_1.next(); !RESERVED_PARAMETER_NAMES_1_1.done; RESERVED_PARAMETER_NAMES_1_1 = RESERVED_PARAMETER_NAMES_1.next()) {
1393
- var reservedParameterName = RESERVED_PARAMETER_NAMES_1_1.value;
1394
- resovedParameters = __spreadArray(__spreadArray([], __read(resovedParameters), false), [reservedParameterName], false);
1471
+ catch (e_6_1) { e_6 = { error: e_6_1 }; }
1472
+ finally {
1473
+ try {
1474
+ if (_o && !_o.done && (_f = _m.return)) _f.call(_m);
1475
+ }
1476
+ finally { if (e_6) throw e_6.error; }
1477
+ }
1478
+ } /* not else */
1479
+ if (format) {
1480
+ if (format === 'JSON') {
1481
+ // TODO: @deprecated remove
1482
+ commands_1.push("FORMAT JSON");
1483
+ }
1484
+ } /* not else */
1485
+ pipelineString += '\n\n';
1486
+ pipelineString += commands_1.map(function (command) { return "- ".concat(command); }).join('\n');
1487
+ pipelineString += '\n\n';
1488
+ pipelineString += '```' + contentLanguage;
1489
+ pipelineString += '\n';
1490
+ pipelineString += spaceTrim__default["default"](content);
1491
+ // <- TODO: [main] !!3 Escape
1492
+ // <- TODO: [🧠] Some clear strategy how to spaceTrim the blocks
1493
+ pipelineString += '\n';
1494
+ pipelineString += '```';
1495
+ pipelineString += '\n\n';
1496
+ pipelineString += "`-> {".concat(resultingParameterName, "}`"); // <- TODO: [main] !!3 If the parameter here has description, add it and use taskParameterJsonToString
1395
1497
  }
1396
1498
  }
1397
1499
  catch (e_3_1) { e_3 = { error: e_3_1 }; }
1398
1500
  finally {
1399
1501
  try {
1400
- if (RESERVED_PARAMETER_NAMES_1_1 && !RESERVED_PARAMETER_NAMES_1_1.done && (_c = RESERVED_PARAMETER_NAMES_1.return)) _c.call(RESERVED_PARAMETER_NAMES_1);
1502
+ if (tasks_1_1 && !tasks_1_1.done && (_c = tasks_1.return)) _c.call(tasks_1);
1401
1503
  }
1402
1504
  finally { if (e_3) throw e_3.error; }
1403
1505
  }
1404
- var unresovedTasks = __spreadArray([], __read(pipeline.tasks), false);
1405
- var loopLimit = LOOP_LIMIT;
1406
- var _loop_3 = function () {
1407
- if (loopLimit-- < 0) {
1408
- // Note: Really UnexpectedError not LimitReachedError - this should not happen and be caught below
1409
- throw new UnexpectedError(spaceTrim.spaceTrim(function (block) { return "\n Loop limit reached during detection of circular dependencies in `validatePipeline`\n\n ".concat(block(pipelineIdentification), "\n "); }));
1410
- }
1411
- var currentlyResovedTasks = unresovedTasks.filter(function (task) {
1412
- return task.dependentParameterNames.every(function (name) { return resovedParameters.includes(name); });
1413
- });
1414
- if (currentlyResovedTasks.length === 0) {
1415
- throw new PipelineLogicError(
1416
- // TODO: [🐎] DRY
1417
- spaceTrim.spaceTrim(function (block) { return "\n\n Can not resolve some parameters:\n Either you are using a parameter that is not defined, or there are some circular dependencies.\n\n ".concat(block(pipelineIdentification), "\n\n **Can not resolve:**\n ").concat(block(unresovedTasks
1418
- .map(function (_a) {
1419
- var resultingParameterName = _a.resultingParameterName, dependentParameterNames = _a.dependentParameterNames;
1420
- return "- Parameter `{".concat(resultingParameterName, "}` which depends on ").concat(dependentParameterNames
1421
- .map(function (dependentParameterName) { return "`{".concat(dependentParameterName, "}`"); })
1422
- .join(' and '));
1423
- })
1424
- .join('\n')), "\n\n **Resolved:**\n ").concat(block(resovedParameters
1425
- .filter(function (name) {
1426
- return !RESERVED_PARAMETER_NAMES.includes(name);
1427
- })
1428
- .map(function (name) { return "- Parameter `{".concat(name, "}`"); })
1429
- .join('\n')), "\n\n\n **Reserved (which are available):**\n ").concat(block(resovedParameters
1430
- .filter(function (name) {
1431
- return RESERVED_PARAMETER_NAMES.includes(name);
1432
- })
1433
- .map(function (name) { return "- Parameter `{".concat(name, "}`"); })
1434
- .join('\n')), "\n\n\n "); }));
1435
- }
1436
- resovedParameters = __spreadArray(__spreadArray([], __read(resovedParameters), false), __read(currentlyResovedTasks.map(function (_a) {
1437
- var resultingParameterName = _a.resultingParameterName;
1438
- return resultingParameterName;
1439
- })), false);
1440
- unresovedTasks = unresovedTasks.filter(function (task) { return !currentlyResovedTasks.includes(task); });
1441
- };
1442
- while (unresovedTasks.length > 0) {
1443
- _loop_3();
1444
- }
1445
- // Note: Check that formfactor is corresponding to the pipeline interface
1446
- // TODO: !!6 Implement this
1447
- // pipeline.formfactorName
1506
+ return validatePipelineString(pipelineString);
1448
1507
  }
1449
1508
  /**
1450
- * TODO: [🧞‍♀️] Do not allow joker + foreach
1451
- * TODO: [🧠] Work with promptbookVersion
1452
- * TODO: Use here some json-schema, Zod or something similar and change it to:
1453
- * > /**
1454
- * > * Validates PipelineJson if it is logically valid.
1455
- * > *
1456
- * > * It checks:
1457
- * > * - it has a valid structure
1458
- * > * - ...
1459
- * > ex port function validatePipeline(promptbook: really_unknown): asserts promptbook is PipelineJson {
1509
+ * @private internal utility of `pipelineJsonToString`
1460
1510
  */
1511
+ function taskParameterJsonToString(taskParameterJson) {
1512
+ var name = taskParameterJson.name, description = taskParameterJson.description;
1513
+ var parameterString = "{".concat(name, "}");
1514
+ if (description) {
1515
+ parameterString = "".concat(parameterString, " ").concat(description);
1516
+ }
1517
+ return parameterString;
1518
+ }
1461
1519
  /**
1462
- * TODO: [🧳][main] !!4 Validate that all examples match expectations
1463
- * TODO: [🧳][🐝][main] !!4 Validate that knowledge is valid (non-void)
1464
- * TODO: [🧳][main] !!4 Validate that persona can be used only with CHAT variant
1465
- * TODO: [🧳][main] !!4 Validate that parameter with reserved name not used RESERVED_PARAMETER_NAMES
1466
- * TODO: [🧳][main] !!4 Validate that reserved parameter is not used as joker
1467
- * TODO: [🧠] Validation not only logic itself but imports around - files and websites and rerefenced pipelines exists
1468
- * TODO: [🛠] Actions, instruments (and maybe knowledge) => Functions and tools
1520
+ * TODO: [🛋] Implement new features and commands into `pipelineJsonToString` + `taskParameterJsonToString` , use `stringifyCommand`
1521
+ * TODO: [🧠] Is there a way to auto-detect missing features in pipelineJsonToString
1522
+ * TODO: [🏛] Maybe make some markdown builder
1523
+ * TODO: [🏛] Escape all
1524
+ * TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
1469
1525
  */
1470
1526
 
1471
1527
  /**
@@ -11295,17 +11351,17 @@
11295
11351
  }
11296
11352
  _a = options || {}, _b = _a.isRecursive, isRecursive = _b === void 0 ? true : _b, _c = _a.isVerbose, isVerbose = _c === void 0 ? DEFAULT_IS_VERBOSE : _c, _d = _a.isLazyLoaded, isLazyLoaded = _d === void 0 ? false : _d, _e = _a.isCrashedOnError, isCrashedOnError = _e === void 0 ? true : _e, rootUrl = _a.rootUrl;
11297
11353
  collection = createCollectionFromPromise(function () { return __awaiter(_this, void 0, void 0, function () {
11298
- var fileNames, collection, _loop_1, fileNames_1, fileNames_1_1, fileName, e_1_1;
11299
- var e_1, _a;
11300
- return __generator(this, function (_b) {
11301
- switch (_b.label) {
11354
+ var fileNames, collection, pipelinesWithFilenames, _loop_1, fileNames_1, fileNames_1_1, fileName, e_1_1, _loop_2, pipelinesWithFilenames_1, pipelinesWithFilenames_1_1, pipelineWithFilenames;
11355
+ var e_1, _a, e_2, _b;
11356
+ return __generator(this, function (_c) {
11357
+ switch (_c.label) {
11302
11358
  case 0:
11303
11359
  if (isVerbose) {
11304
11360
  console.info(colors__default["default"].cyan("Creating pipeline collection from path ".concat(rootPath.split('\\').join('/'))));
11305
11361
  }
11306
11362
  return [4 /*yield*/, listAllFiles(rootPath, isRecursive, tools.fs)];
11307
11363
  case 1:
11308
- fileNames = _b.sent();
11364
+ fileNames = _c.sent();
11309
11365
  // Note: First load compiled `.bookc` files and then source `.book` files
11310
11366
  // `.bookc` are already compiled and can be used faster
11311
11367
  fileNames.sort(function (a, b) {
@@ -11318,100 +11374,54 @@
11318
11374
  return 0;
11319
11375
  });
11320
11376
  collection = new Map();
11377
+ pipelinesWithFilenames = [];
11321
11378
  _loop_1 = function (fileName) {
11322
- var sourceFile, rootDirname, pipeline, pipelineString, _c, _d, _e, pipelineUrl, existing, error_1, wrappedErrorMessage;
11323
- return __generator(this, function (_f) {
11324
- switch (_f.label) {
11379
+ var sourceFile, rootDirname, pipelineString, _d, pipeline, _e, _f, _g, _h, error_1, wrappedErrorMessage;
11380
+ return __generator(this, function (_j) {
11381
+ switch (_j.label) {
11325
11382
  case 0:
11326
11383
  sourceFile = './' + fileName.split('\\').join('/');
11327
11384
  rootDirname = path.dirname(sourceFile).split('\\').join('/');
11328
- _f.label = 1;
11385
+ _j.label = 1;
11329
11386
  case 1:
11330
- _f.trys.push([1, 8, , 9]);
11331
- pipeline = null;
11387
+ _j.trys.push([1, 8, , 9]);
11332
11388
  if (!(fileName.endsWith('.book') || fileName.endsWith('.book.md'))) return [3 /*break*/, 4];
11333
- _c = validatePipelineString;
11389
+ _d = validatePipelineString;
11334
11390
  return [4 /*yield*/, promises.readFile(fileName, 'utf-8')];
11335
11391
  case 2:
11336
- pipelineString = _c.apply(void 0, [_f.sent()]);
11392
+ pipelineString = _d.apply(void 0, [_j.sent()]);
11337
11393
  return [4 /*yield*/, compilePipeline(pipelineString, tools, {
11338
11394
  rootDirname: rootDirname,
11339
11395
  })];
11340
11396
  case 3:
11341
- pipeline = _f.sent();
11342
- pipeline = __assign(__assign({}, pipeline), { sourceFile: sourceFile });
11397
+ pipeline = _j.sent();
11398
+ pipelinesWithFilenames.push({ fileName: fileName, sourceFile: sourceFile, pipeline: __assign(__assign({}, pipeline), { sourceFile: sourceFile }) });
11343
11399
  return [3 /*break*/, 7];
11344
11400
  case 4:
11345
11401
  if (!(fileName.endsWith('.bookc') || fileName.endsWith('.book.json'))) return [3 /*break*/, 6];
11346
- _e = (_d = JSON).parse;
11347
- return [4 /*yield*/, promises.readFile(fileName, 'utf-8')];
11402
+ _f =
11403
+ // TODO: Handle non-valid JSON files
11404
+ (_e = pipelinesWithFilenames.push).apply;
11405
+ _g = [
11406
+ // TODO: Handle non-valid JSON files
11407
+ pipelinesWithFilenames];
11408
+ _h = [[]];
11409
+ return [4 /*yield*/, loadArchive(fileName, tools.fs)];
11348
11410
  case 5:
11349
11411
  // TODO: Handle non-valid JSON files
11350
- pipeline = _e.apply(_d, [_f.sent()]);
11351
- // TODO: [🌗]
11352
- pipeline = __assign(__assign({}, pipeline), { sourceFile: sourceFile });
11412
+ _f.apply(_e, _g.concat([__spreadArray.apply(void 0, _h.concat([__read.apply(void 0, [(_j.sent()).map(function (pipeline) {
11413
+ // TODO: [🌗]
11414
+ return ({ fileName: fileName, sourceFile: sourceFile, pipeline: __assign(__assign({}, pipeline), { sourceFile: sourceFile }) });
11415
+ })]), false]))]));
11353
11416
  return [3 /*break*/, 7];
11354
11417
  case 6:
11355
11418
  if (isVerbose) {
11356
11419
  console.info(colors__default["default"].gray("Skipped file ".concat(fileName.split('\\').join('/'), " \u2013\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060 Not a book")));
11357
11420
  }
11358
- _f.label = 7;
11359
- case 7:
11360
- // ---
11361
- if (pipeline !== null) {
11362
- if (rootUrl !== undefined) {
11363
- if (pipeline.pipelineUrl === undefined) {
11364
- pipelineUrl = rootUrl + '/' + path.relative(rootPath, fileName).split('\\').join('/');
11365
- // console.log({ pipelineUrl, rootPath, rootUrl, fileName });
11366
- if (isVerbose) {
11367
- console.info(colors__default["default"].yellow("Implicitly set pipeline URL to ".concat(pipelineUrl, " from ").concat(fileName
11368
- .split('\\')
11369
- .join('/'))));
11370
- }
11371
- pipeline = __assign(__assign({}, pipeline), { pipelineUrl: pipelineUrl });
11372
- }
11373
- else if (!pipeline.pipelineUrl.startsWith(rootUrl)) {
11374
- throw new PipelineUrlError(spaceTrim__default["default"]("\n Pipeline with URL ".concat(pipeline.pipelineUrl, " is not a child of the root URL ").concat(rootUrl, " \uD83C\uDF4F\n\n File:\n ").concat(sourceFile || 'Unknown', "\n\n ")));
11375
- }
11376
- }
11377
- // TODO: [👠] DRY
11378
- if (pipeline.pipelineUrl === undefined) {
11379
- if (isVerbose) {
11380
- console.info(colors__default["default"].yellow("Can not load pipeline from ".concat(fileName
11381
- .split('\\')
11382
- .join('/'), " because of missing URL")));
11383
- }
11384
- }
11385
- else {
11386
- // Note: [🐨] Pipeline is checked multiple times
11387
- // TODO: Maybe once is enough BUT be sure to check it - better to check it multiple times than not at all
11388
- validatePipeline(pipeline);
11389
- if (
11390
- // TODO: [🐽] comparePipelines(pipeline1,pipeline2): 'IDENTICAL' |'IDENTICAL_UNPREPARED' | 'IDENTICAL_INTERFACE' | 'DIFFERENT'
11391
- !collection.has(pipeline.pipelineUrl)) {
11392
- if (isVerbose) {
11393
- console.info(colors__default["default"].green("Loaded pipeline ".concat(fileName.split('\\').join('/'), "\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060")));
11394
- }
11395
- // Note: [🦄] Pipeline with same url uniqueness will be double-checked automatically in SimplePipelineCollection
11396
- collection.set(pipeline.pipelineUrl, pipeline);
11397
- }
11398
- else if (pipelineJsonToString(unpreparePipeline(pipeline)) ===
11399
- pipelineJsonToString(unpreparePipeline(collection.get(pipeline.pipelineUrl)))) {
11400
- if (isVerbose) {
11401
- console.info(colors__default["default"].gray("Skipped pipeline ".concat(fileName
11402
- .split('\\')
11403
- .join('/'), " \u2013\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060 Already identical pipeline in the collection")));
11404
- }
11405
- }
11406
- else {
11407
- existing = collection.get(pipeline.pipelineUrl);
11408
- throw new PipelineUrlError(spaceTrim__default["default"]("\n Pipeline with URL ".concat(pipeline.pipelineUrl, " is already in the collection \uD83C\uDF4F\n\n Conflicting files:\n ").concat(existing.sourceFile || 'Unknown', "\n ").concat(pipeline.sourceFile || 'Unknown', "\n\n Note: You have probably forgotten to run \"ptbk make\" to update the collection\n Note: Pipelines with the same URL are not allowed\n Only exepction is when the pipelines are identical\n\n ")));
11409
- }
11410
- }
11411
- }
11412
- return [3 /*break*/, 9];
11421
+ _j.label = 7;
11422
+ case 7: return [3 /*break*/, 9];
11413
11423
  case 8:
11414
- error_1 = _f.sent();
11424
+ error_1 = _j.sent();
11415
11425
  if (!(error_1 instanceof Error)) {
11416
11426
  throw error_1;
11417
11427
  }
@@ -11426,24 +11436,24 @@
11426
11436
  }
11427
11437
  });
11428
11438
  };
11429
- _b.label = 2;
11439
+ _c.label = 2;
11430
11440
  case 2:
11431
- _b.trys.push([2, 7, 8, 9]);
11441
+ _c.trys.push([2, 7, 8, 9]);
11432
11442
  fileNames_1 = __values(fileNames), fileNames_1_1 = fileNames_1.next();
11433
- _b.label = 3;
11443
+ _c.label = 3;
11434
11444
  case 3:
11435
11445
  if (!!fileNames_1_1.done) return [3 /*break*/, 6];
11436
11446
  fileName = fileNames_1_1.value;
11437
11447
  return [5 /*yield**/, _loop_1(fileName)];
11438
11448
  case 4:
11439
- _b.sent();
11440
- _b.label = 5;
11449
+ _c.sent();
11450
+ _c.label = 5;
11441
11451
  case 5:
11442
11452
  fileNames_1_1 = fileNames_1.next();
11443
11453
  return [3 /*break*/, 3];
11444
11454
  case 6: return [3 /*break*/, 9];
11445
11455
  case 7:
11446
- e_1_1 = _b.sent();
11456
+ e_1_1 = _c.sent();
11447
11457
  e_1 = { error: e_1_1 };
11448
11458
  return [3 /*break*/, 9];
11449
11459
  case 8:
@@ -11452,7 +11462,86 @@
11452
11462
  }
11453
11463
  finally { if (e_1) throw e_1.error; }
11454
11464
  return [7 /*endfinally*/];
11455
- case 9: return [2 /*return*/, Array.from(collection.values())];
11465
+ case 9:
11466
+ _loop_2 = function (pipelineWithFilenames) {
11467
+ var fileName = pipelineWithFilenames.fileName, sourceFile = pipelineWithFilenames.sourceFile;
11468
+ var pipeline = pipelineWithFilenames.pipeline;
11469
+ try {
11470
+ if (rootUrl !== undefined) {
11471
+ if (pipeline.pipelineUrl === undefined) {
11472
+ var pipelineUrl = rootUrl + '/' + path.relative(rootPath, fileName).split('\\').join('/');
11473
+ // console.log({ pipelineUrl, rootPath, rootUrl, fileName });
11474
+ if (isVerbose) {
11475
+ console.info(colors__default["default"].yellow("Implicitly set pipeline URL to ".concat(pipelineUrl, " from ").concat(fileName
11476
+ .split('\\')
11477
+ .join('/'))));
11478
+ }
11479
+ pipeline = __assign(__assign({}, pipeline), { pipelineUrl: pipelineUrl });
11480
+ }
11481
+ else if (!pipeline.pipelineUrl.startsWith(rootUrl)) {
11482
+ throw new PipelineUrlError(spaceTrim__default["default"]("\n Pipeline with URL ".concat(pipeline.pipelineUrl, " is not a child of the root URL ").concat(rootUrl, " \uD83C\uDF4F\n\n File:\n ").concat(sourceFile || 'Unknown', "\n\n ")));
11483
+ }
11484
+ }
11485
+ // TODO: [👠] DRY
11486
+ if (pipeline.pipelineUrl === undefined) {
11487
+ if (isVerbose) {
11488
+ console.info(colors__default["default"].yellow("Can not load pipeline from ".concat(fileName.split('\\').join('/'), " because of missing URL")));
11489
+ }
11490
+ }
11491
+ else {
11492
+ // Note: [🐨] Pipeline is checked multiple times
11493
+ // TODO: Maybe once is enough BUT be sure to check it - better to check it multiple times than not at all
11494
+ validatePipeline(pipeline);
11495
+ if (
11496
+ // TODO: [🐽] comparePipelines(pipeline1,pipeline2): 'IDENTICAL' |'IDENTICAL_UNPREPARED' | 'IDENTICAL_INTERFACE' | 'DIFFERENT'
11497
+ !collection.has(pipeline.pipelineUrl)) {
11498
+ if (isVerbose) {
11499
+ console.info(colors__default["default"].green("Loaded pipeline ".concat(fileName.split('\\').join('/'), "\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060")));
11500
+ }
11501
+ // Note: [🦄] Pipeline with same url uniqueness will be double-checked automatically in SimplePipelineCollection
11502
+ collection.set(pipeline.pipelineUrl, pipeline);
11503
+ }
11504
+ else if (pipelineJsonToString(unpreparePipeline(pipeline)) ===
11505
+ pipelineJsonToString(unpreparePipeline(collection.get(pipeline.pipelineUrl)))) {
11506
+ if (isVerbose) {
11507
+ console.info(colors__default["default"].gray("Skipped pipeline ".concat(fileName
11508
+ .split('\\')
11509
+ .join('/'), " \u2013\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060\u2060 Already identical pipeline in the collection")));
11510
+ }
11511
+ }
11512
+ else {
11513
+ var existing = collection.get(pipeline.pipelineUrl);
11514
+ throw new PipelineUrlError(spaceTrim__default["default"]("\n Pipeline with URL ".concat(pipeline.pipelineUrl, " is already in the collection \uD83C\uDF4F\n\n Conflicting files:\n ").concat(existing.sourceFile || 'Unknown', "\n ").concat(pipeline.sourceFile || 'Unknown', "\n\n Note: You have probably forgotten to run \"ptbk make\" to update the collection\n Note: Pipelines with the same URL are not allowed\n Only exepction is when the pipelines are identical\n\n ")));
11515
+ }
11516
+ }
11517
+ }
11518
+ catch (error) {
11519
+ if (!(error instanceof Error)) {
11520
+ throw error;
11521
+ }
11522
+ // TODO: [7] DRY
11523
+ var wrappedErrorMessage = spaceTrim__default["default"](function (block) { return "\n ".concat(error.name, " in pipeline ").concat(fileName.split('\\').join('/'), "\u2060:\n\n Original error message:\n ").concat(block(error.message), "\n\n Original stack trace:\n ").concat(block(error.stack || ''), "\n\n ---\n\n "); }) + '\n';
11524
+ if (isCrashedOnError) {
11525
+ throw new CollectionError(wrappedErrorMessage);
11526
+ }
11527
+ // TODO: [🟥] Detect browser / node and make it colorfull
11528
+ console.error(wrappedErrorMessage);
11529
+ }
11530
+ };
11531
+ try {
11532
+ for (pipelinesWithFilenames_1 = __values(pipelinesWithFilenames), pipelinesWithFilenames_1_1 = pipelinesWithFilenames_1.next(); !pipelinesWithFilenames_1_1.done; pipelinesWithFilenames_1_1 = pipelinesWithFilenames_1.next()) {
11533
+ pipelineWithFilenames = pipelinesWithFilenames_1_1.value;
11534
+ _loop_2(pipelineWithFilenames);
11535
+ }
11536
+ }
11537
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
11538
+ finally {
11539
+ try {
11540
+ if (pipelinesWithFilenames_1_1 && !pipelinesWithFilenames_1_1.done && (_b = pipelinesWithFilenames_1.return)) _b.call(pipelinesWithFilenames_1);
11541
+ }
11542
+ finally { if (e_2) throw e_2.error; }
11543
+ }
11544
+ return [2 /*return*/, Array.from(collection.values())];
11456
11545
  }
11457
11546
  });
11458
11547
  }); });
@@ -11517,9 +11606,11 @@
11517
11606
  FileCacheStorage.prototype.getFilenameForKey = function (key) {
11518
11607
  // TODO: [👬] DRY
11519
11608
  var name = titleToName(key);
11609
+ var nameStart = name.split('-', 2)[0] || 'unnamed';
11520
11610
  var hash = sha256__default["default"](hexEncoder__default["default"].parse(name)).toString( /* hex */);
11521
11611
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
11522
- return path.join.apply(void 0, __spreadArray(__spreadArray([this.options.rootFolderPath], __read(nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */)), false), ["".concat(name.substring(0, MAX_FILENAME_LENGTH), ".json")], false));
11612
+ return path.join.apply(void 0, __spreadArray(__spreadArray([this.options.rootFolderPath,
11613
+ nameStart], __read(nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */)), false), ["".concat(name.substring(0, MAX_FILENAME_LENGTH), ".json")], false));
11523
11614
  };
11524
11615
  /**
11525
11616
  * @@@ Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.