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